diff --git a/src/content/__init__.py b/src/content/__init__.py index e69de29..0f5a197 100644 --- a/src/content/__init__.py +++ b/src/content/__init__.py @@ -0,0 +1,4 @@ +""" Kasu Content App + +This Django App is a very small an reduced CMS and News-Blogging application. +""" diff --git a/src/content/admin.py b/src/content/admin.py index f9cb2a9..3c2c22f 100644 --- a/src/content/admin.py +++ b/src/content/admin.py @@ -1,3 +1,9 @@ +"""Django admin interface for the content app. + +you will not need the admin interface that often, many tasks can be resoved via + the user frontend. +""" + from django.contrib import admin from . import models @@ -26,7 +32,7 @@ class CategoryAdmin(admin.ModelAdmin): class PageAdmin(admin.ModelAdmin): """ - Admin interface for static pages that can contain HTML or PDF Content. + Admin interface for static pages that can contain HTML or PDF Content. """ prepopulated_fields = {"slug": ('menu_name_de',)} inlines = [PageTabularInline, ] diff --git a/src/content/context_processors.py b/src/content/context_processors.py index fb29cd1..1cd1456 100644 --- a/src/content/context_processors.py +++ b/src/content/context_processors.py @@ -1,8 +1,4 @@ -# -*- encoding: UTF-8 -*- -""" -Created on 30.09.2011 - -@author: christian +""" Django contextprocessor to have dynamicly generated menus in the templates. """ from django.core.cache import cache @@ -10,6 +6,18 @@ from . import models def content_menus(request): + """ Generate the menu tree and add these info to the template context. + + The following variables will be added to the template context: + - top_menu_items: QuerySet with all top level pages (children of the root) + - current_top_page: Page Object of the top level page that is currently + shown to the user, or it's childen are shown to the user. + - current_path: the path part of the current URL, + - current_page: the current Page object that is associated with this URL + + :param request: a Django request object + :return: a dict with the template variables mentioned above + """ current_page = None current_top_page = None current_path = request.path_info[1:request.path_info.rfind('.')] @@ -36,7 +44,7 @@ def content_menus(request): all_pages = dict((path, page_id) for path, page_id in all_pages) cache.set('all_pages', all_pages, 360) - while len(current_path) > 0: + while current_path: if current_path in all_pages: current_page = models.Page.objects.get(pk=all_pages[current_path]) break @@ -45,5 +53,4 @@ def content_menus(request): return {'top_menu_items': top_level_pages, 'current_top_page': current_top_page, 'current_path': current_path, - 'current_page': current_page - } + 'current_page': current_page} diff --git a/src/content/feeds.py b/src/content/feeds.py index ec29400..8e0435b 100644 --- a/src/content/feeds.py +++ b/src/content/feeds.py @@ -1,40 +1,49 @@ -from datetime import datetime, time - -from django.conf import settings -from django.utils.translation import ugettext as _ +"""RSS and Atom Feeds to subscribe to the latest news or comments.""" import django_comments as comments +from django.conf import settings from django.contrib.syndication.views import Feed from django.utils.feedgenerator import Rss201rev2Feed +from django.utils.translation import ugettext as _ from content.models import Article +MAX_ARTICLE_ITEMS = 10 # Maximum count of articles in the news RSS feed. +MAX_COMMENT_ITEMS = 40 # Maximum count of comments in the comments RSS feed. + +# Start ignoring PyLintBear (R0201) class LatestNews(Feed): + """ An Feed with the latest news from this site """ link = "http://www.kasu.at/" description = _("Current news from Kasu") title = "Kasu - traditonelle asiatische Spielkultur" feed_type = Rss201rev2Feed def items(self): - return Article.objects.published()[:10] + """get the newest published news articles for the feed.""" + return Article.objects.published()[:MAX_ARTICLE_ITEMS] def item_title(self, item): + """Return the article headline as title.""" return item.headline def item_author_name(self, item): + """Return the username from the author of this article.""" return item.author.username def item_categories(self, item): + """Return the name of the category to which this article belongs.""" return item.category.name, def item_description(self, item): + """Return the main content of the article.""" return item.content def item_pubdate(self, item): - return datetime.combine(item.date_created, time()) + """Return the creation date as date of publication.""" + return item.date_created -# noinspection PyMethodMayBeStatic class LatestComments(Feed): """Feed of latest comments on the current site.""" @@ -44,23 +53,30 @@ class LatestComments(Feed): feed_type = Rss201rev2Feed def items(self): + """Get the newest published comments for the feed.""" qs = comments.get_model().objects.filter( site__pk=settings.SITE_ID, is_public=True, is_removed=False ) - return qs.order_by('-submit_date')[:40] + return qs.order_by('-submit_date')[:MAX_COMMENT_ITEMS] def item_author_name(self, item): + """Return the username of the comment author.""" return item.user_name def item_description(self, item): + """Return the whole comment text.""" return item.comment def item_pubdate(self, item): + """Return the submit date as publication date.""" return item.submit_date def item_title(self, item): + """Return the author and the pagetitle that has been commented.""" return 'From %(user_name)s in %(content_object)s' % { 'user_name': item.user_name, 'content_object': item.content_object } + +# Stop ignoring diff --git a/src/content/forms.py b/src/content/forms.py index 7540422..f2b3883 100644 --- a/src/content/forms.py +++ b/src/content/forms.py @@ -11,10 +11,12 @@ from . import models class ArticleForm(forms.ModelForm): + """ Django form for adding and editing news articles. """ error_css_class = 'error' required_css_class = 'required' class Meta(object): + """Only the content should be visible, hide the meta data.""" fields = ( 'headline_de', 'content_de', 'headline_en', 'content_en', @@ -23,15 +25,18 @@ class ArticleForm(forms.ModelForm): ) model = models.Article - def save(self, force_insert=False, force_update=False, commit=True): - article = super(ArticleForm, self).save(commit=False) - article.slug = slugify(article.headline_de)[:50] - if commit: - article.save(force_insert=force_insert, force_update=force_update) - return article + def save(self, commit=True): + """ slugify the german headline and set is as slug before saving. + + :param commit: Save this form's self.instance object if True + :return: self.instance object.""" + self.instance.slug = slugify(self.instance.headline_de)[:50] + return super(ArticleForm, self).save(commit=commit) class PageForm(forms.ModelForm): + """ Django form for adding and editing static pages.""" + error_css_class = 'error' required_css_class = 'required' content_type = forms.ChoiceField( @@ -40,13 +45,14 @@ class PageForm(forms.ModelForm): ) class Meta(object): + """Only the content should be visible, hide the meta data.""" exclude = ('position',) model = models.Page def clean(self): + """Check if a file has been uploaded if the page is marked as PDF.""" cleaned_data = super(PageForm, self).clean() content_type = cleaned_data.get("content_type") - cleaned_data.get("pdf_de") if content_type == "2" and not cleaned_data.get("pdf_de"): msg = _('Please upload a PDF-File to this PDF-Page.') self._errors["content_type"] = self.error_class([msg]) diff --git a/src/content/locale/de/LC_MESSAGES/django.po b/src/content/locale/de/LC_MESSAGES/django.po index 0a91ae6..2193de5 100644 --- a/src/content/locale/de/LC_MESSAGES/django.po +++ b/src/content/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: kasu.content\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-05-10 23:16+0200\n" +"POT-Creation-Date: 2017-06-19 22:46+0200\n" "PO-Revision-Date: 2016-09-28 00:24+0200\n" "Last-Translator: Christian Berg \n" "Language-Team: Deutsch <>\n" @@ -20,147 +20,147 @@ msgstr "" "X-Translated-Using: django-rosetta 0.7.2\n" "X-Generator: Poedit 1.8.9\n" -#: content/feeds.py:14 +#: content/feeds.py:16 msgid "Current news from Kasu" msgstr "Aktuelle Nachrichten von Kasu" -#: content/feeds.py:42 +#: content/feeds.py:43 msgid "Latest comments on kasu.at" msgstr "Neueste Kommentare auf Kasu.at " -#: content/feeds.py:43 +#: content/feeds.py:44 msgid "Kasu - latest comments" msgstr "Kasu - neue Kommentare" -#: content/forms.py:52 content/models.py:249 +#: content/forms.py:52 content/models.py:308 msgid "Please upload a PDF-File to this PDF-Page." msgstr "Bitte eine PDF Datei für diese PDF Seite hochladen." -#: content/models.py:51 +#: content/models.py:62 msgid "Headline" msgstr "Schlagzeile" -#: content/models.py:53 +#: content/models.py:64 msgid "Content" msgstr "Inhalt" -#: content/models.py:55 content/models.py:279 +#: content/models.py:66 content/models.py:137 #: content/templates/content/article_detail.html:25 msgid "Category" msgstr "Kategorie" -#: content/models.py:56 content/models.py:273 +#: content/models.py:67 content/models.py:130 msgid "Image" msgstr "Bild" -#: content/models.py:58 content/models.py:275 +#: content/models.py:69 content/models.py:132 msgid "Slug" msgstr "Slug" -#: content/models.py:60 content/templates/content/article_detail.html:23 +#: content/models.py:71 content/templates/content/article_detail.html:23 msgid "Author" msgstr "Autor" -#: content/models.py:61 +#: content/models.py:72 msgid "Status" msgstr "Status" -#: content/models.py:63 +#: content/models.py:74 msgid "Created" msgstr "Erstellt" -#: content/models.py:64 +#: content/models.py:75 msgid "Modified" msgstr "Bearbeitet" -#: content/models.py:68 +#: content/models.py:80 msgid "Article" msgstr "Artikel" -#: content/models.py:69 +#: content/models.py:81 msgid "Articles" msgstr "Artikel" -#: content/models.py:126 content/models.py:132 +#: content/models.py:126 content/models.py:127 +msgid "Name" +msgstr "Name" + +#: content/models.py:128 content/models.py:129 +msgid "Description" +msgstr "Beschreibung" + +#: content/models.py:138 +msgid "Categories" +msgstr "Kategorien" + +#: content/models.py:169 content/models.py:175 msgid "The short name for the menu-entry of this page" msgstr "Ein kurzer Name für den Menüeintrag" -#: content/models.py:137 content/models.py:142 +#: content/models.py:180 content/models.py:185 msgid "The page title as you'd like it to be seen by the public" msgstr "" -#: content/models.py:144 +#: content/models.py:187 msgid "slug" msgstr "Slug" -#: content/models.py:146 +#: content/models.py:190 msgid "" "The name of the page as it will appear in URLs e.g http://domain.com/blog/" "[my-slug]/" msgstr "" -#: content/models.py:153 +#: content/models.py:199 msgid "Path" msgstr "Pfad" -#: content/models.py:165 +#: content/models.py:211 msgid "Position" msgstr "Position" -#: content/models.py:170 +#: content/models.py:216 msgid "status" msgstr "Status" -#: content/models.py:172 content/models.py:173 +#: content/models.py:219 content/models.py:221 #, fuzzy #| msgid "Description" msgid "search description" msgstr "Beschreibung" -#: content/models.py:176 +#: content/models.py:224 #, fuzzy #| msgid "Content" msgid "content type" msgstr "Inhalt" -#: content/models.py:181 +#: content/models.py:229 msgid "enable comments" msgstr "Kommentare möglich" -#: content/models.py:186 +#: content/models.py:234 msgid "Template" msgstr "Vorlage" -#: content/models.py:194 +#: content/models.py:242 #, fuzzy #| msgid "created on" msgid "first created at" msgstr "erstellt am" -#: content/models.py:199 +#: content/models.py:247 msgid "latest updated at" msgstr "" -#: content/models.py:264 +#: content/models.py:322 msgid "Page" msgstr "Seite" -#: content/models.py:265 +#: content/models.py:323 msgid "Pages" msgstr "Seiten" -#: content/models.py:269 content/models.py:270 -msgid "Name" -msgstr "Name" - -#: content/models.py:271 content/models.py:272 -msgid "Description" -msgstr "Beschreibung" - -#: content/models.py:280 -msgid "Categories" -msgstr "Kategorien" - #: content/templates/content/article_archive.html:5 #: content/templates/content/article_archive.html:20 msgid "Article Archive" @@ -172,31 +172,32 @@ msgstr "Nachrichtenarchiv" msgid "Archive" msgstr "Archiv" -#: content/templates/content/article_archive.html:52 +#: content/templates/content/article_archive.html:56 msgid "All Categories" msgstr "Alle Kategorien" -#: content/templates/content/article_archive.html:67 +#: content/templates/content/article_archive.html:71 msgid "created on" msgstr "erstellt am" -#: content/templates/content/article_archive.html:68 +#: content/templates/content/article_archive.html:73 msgid "by" msgstr "von" -#: content/templates/content/article_archive.html:69 +#: content/templates/content/article_archive.html:74 +#: content/templates/content/article_archive.html:75 msgid "comments" msgstr "Kommentare" -#: content/templates/content/article_archive.html:73 +#: content/templates/content/article_archive.html:81 msgid "Read More" msgstr "Mehr lesen" -#: content/templates/content/article_archive.html:76 +#: content/templates/content/article_archive.html:86 msgid "We're sorry. Your search yielded no results." msgstr "Es tut uns leid. Deine Suche ergab keine Treffer." -#: content/templates/content/article_archive.html:94 +#: content/templates/content/article_archive.html:104 msgid "Add Article" msgstr "neuer Artikel " @@ -213,32 +214,32 @@ msgid "share on" msgstr "Teile auf" #: content/templates/content/article_detail.html:51 -#: content/templates/content/article_form.html:24 content/views.py:88 +#: content/templates/content/article_form.html:20 content/views.py:138 msgid "Edit Article" msgstr "Artikel bearbeiten" -#: content/templates/content/article_form.html:24 content/views.py:90 +#: content/templates/content/article_form.html:20 content/views.py:139 msgid "Create Article" msgstr "Artikel erstellen" -#: content/templates/content/article_form.html:29 +#: content/templates/content/article_form.html:25 #: content/templates/content/page_form.html:49 #: content/templates/content/page_form.html:56 msgid "German" msgstr "Deutsch" -#: content/templates/content/article_form.html:30 +#: content/templates/content/article_form.html:26 #: content/templates/content/page_form.html:50 #: content/templates/content/page_form.html:64 msgid "English" msgstr "Englisch" -#: content/templates/content/article_form.html:49 +#: content/templates/content/article_form.html:45 #: content/templates/content/page_form.html:73 msgid "reset" msgstr "Zurücksetzen" -#: content/templates/content/article_form.html:50 +#: content/templates/content/article_form.html:46 #: content/templates/content/page_form.html:74 msgid "save" msgstr "Speichern" @@ -262,16 +263,16 @@ msgstr "Bearbeiten" msgid "HTML Specific" msgstr "HTML spezifisch" -#: content/views.py:23 +#: content/views.py:35 msgid "This Category does not exist." msgstr "Diese Kategorie existiert nicht." -#: content/views.py:157 +#: content/views.py:205 #, python-format msgid "No Page found matching the Path %s" msgstr "Keine Seite unter dem Pfad %s gefunden" -#: content/views.py:172 +#: content/views.py:219 #, python-format msgid "No PDF Document found matching the Path %s" msgstr "Kein PDF Dokument unter dem Pfad %s gefunden." diff --git a/src/content/migrations/0003_auto_20161012_2205.py b/src/content/migrations/0003_auto_20161012_2205.py index ea8a665..148f4da 100644 --- a/src/content/migrations/0003_auto_20161012_2205.py +++ b/src/content/migrations/0003_auto_20161012_2205.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.utils.timezone +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('content', '0002_auto_20150823_2232'), ] @@ -16,13 +15,15 @@ class Migration(migrations.Migration): model_name='page', name='date_created', field=models.DateTimeField(default=django.utils.timezone.now, - verbose_name='first created at', editable=False, db_index=True), + verbose_name='first created at', + editable=False, db_index=True), ), migrations.AddField( model_name='page', name='date_modified', field=models.DateTimeField( - default=django.utils.timezone.now, verbose_name='latest updated at', editable=False), + default=django.utils.timezone.now, + verbose_name='latest updated at', editable=False), ), migrations.AlterField( model_name='article', @@ -34,36 +35,44 @@ class Migration(migrations.Migration): model_name='page', name='content_type', field=models.IntegerField(verbose_name='Inhaltstyp', choices=[ - (0, 'Django View'), (1, 'HTML'), (2, 'PDF')]), + (0, 'Django View'), (1, 'HTML'), (2, 'PDF')]), ), migrations.AlterField( model_name='page', name='path', field=models.CharField( - verbose_name='Pfad', unique=True, max_length=255, editable=False, db_index=True), + verbose_name='Pfad', unique=True, max_length=255, + editable=False, db_index=True), ), migrations.AlterField( model_name='page', name='slug', field=models.SlugField( - help_text='The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/', max_length=100, verbose_name='Slug'), + help_text='The name of the page as it will appear in URLs e.g ' + 'http://domain.com/blog/[my-slug]/', + max_length=100, verbose_name='Slug'), ), migrations.AlterField( model_name='page', name='template', field=models.CharField( - default=b'content/page.html', max_length=255, verbose_name='Vorlage'), + default=b'content/page.html', max_length=255, + verbose_name='Vorlage'), ), migrations.AlterField( model_name='page', name='title_de', field=models.CharField( - help_text="The page title as you'd like it to be seen by the public", max_length=255, verbose_name=b'Titel'), + help_text="The page title as you'd like it to be seen by the " + "public", + max_length=255, verbose_name=b'Titel'), ), migrations.AlterField( model_name='page', name='title_en', - field=models.CharField(help_text="The page title as you'd like it to be seen by the public", - max_length=255, verbose_name=b'Title', blank=True), + field=models.CharField( + help_text="The page title as you'd like it to be seen by the " + "public", + max_length=255, verbose_name=b'Title', blank=True), ), ] diff --git a/src/content/migrations/0004_auto_20161012_2206.py b/src/content/migrations/0004_auto_20161012_2206.py index 4fba0c8..ec5715d 100644 --- a/src/content/migrations/0004_auto_20161012_2206.py +++ b/src/content/migrations/0004_auto_20161012_2206.py @@ -5,7 +5,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ('content', '0003_auto_20161012_2205'), ] @@ -15,7 +14,8 @@ class Migration(migrations.Migration): model_name='page', name='date_created', field=models.DateTimeField( - auto_now_add=True, verbose_name='first created at', db_index=True), + auto_now_add=True, verbose_name='first created at', + db_index=True), ), migrations.AlterField( model_name='page', diff --git a/src/content/models.py b/src/content/models.py index e63e09c..df51317 100644 --- a/src/content/models.py +++ b/src/content/models.py @@ -1,5 +1,5 @@ -# -*- coding: utf-8 -*- -from ckeditor.fields import RichTextField +""" Django Models for a lightwight Content Management.""" +from ckeditor_uploader.fields import RichTextUploadingField from django.conf import settings from django.core.cache import cache from django.core.exceptions import ValidationError @@ -12,23 +12,29 @@ from django.utils.translation import get_language, ugettext as _ from utils import STATUS_CHOICES, STATUS_WAITING, STATUS_PUBLISHED, CLEANER +DJANGO_VIEW = 0 +HTML_PAGE = 1 +PDF_PAGE = 2 CONTENT_CHOICES = ( - (0, u'Django View'), - (1, u'HTML'), - (2, u'PDF') + (DJANGO_VIEW, u'Django View'), + (HTML_PAGE, u'HTML'), + (PDF_PAGE, u'PDF') ) +CONTENT_TYPE_EXTENSIONS = { + DJANGO_VIEW: "/", + HTML_PAGE: ".html", + PDF_PAGE: ".pdf" +} def get_upload_path(instance, filename): - """ - Generates the desired file path and filename for an uploaded Image. + """Generates the desired file path and filename for an uploaded Image. With this function Django can save the uploaded images to subfolders that also have a meaning for humans. - @param instance: an Django Object for which the Image has been uploaded. - @type instance: a instace of an models.Model sub-class. - @param filename: The filename of the uploaded image. - @type filename: String + :param instance: an Django Object for which the Image has been uploaded. + :param filename: The filename of the uploaded image. + :return: relative path and filename for the image on the server. """ extension = filename[filename.rfind('.') + 1:] if isinstance(instance, Category): @@ -36,11 +42,20 @@ def get_upload_path(instance, filename): class ArticleManager(models.Manager): + """Adds some predifined querys and joins some tables for faster querys.""" + def get_queryset(self): + """Join the author and category to the default query for more + perfomance. + + :return: QuerySet""" return super(ArticleManager, self).get_queryset().select_related( 'author', 'category') def published(self): + """Return articles that has been published by now. + + :return: QuerySet""" return self.filter( status=STATUS_PUBLISHED, date_created__lte=timezone.now() @@ -48,10 +63,12 @@ class ArticleManager(models.Manager): class Article(models.Model): + """A news article, simmilar to an blog entry, it can be in german and + english.""" headline_de = models.CharField(_('Headline'), max_length=255) headline_en = models.CharField('Headline', max_length=255, blank=True) - content_de = RichTextField(_('Content')) - content_en = RichTextField('Content', blank=True) + content_de = RichTextUploadingField(_('Content')) + content_en = RichTextUploadingField('Content', blank=True) category = models.ForeignKey('Category', verbose_name=_('Category')) image = models.ImageField(_('Image'), upload_to='news/', blank=True, null=True) @@ -65,11 +82,13 @@ class Article(models.Model): objects = ArticleManager() class Meta(object): + """Sort them by creation date, newes articles first.""" verbose_name = _('Article') verbose_name_plural = _('Articles') ordering = ('-date_created',) def clean(self): + """Give the article an slug and scrub the html code.""" if not self.date_created: self.date_created = timezone.now() if not self.slug: @@ -78,48 +97,78 @@ class Article(models.Model): self.content_en = CLEANER.clean_html(self.content_en) def __str__(self): + """Returns the headline of this article.""" return self.headline @property def get_image(self): - if self.image: - return self.image - else: - return self.category.image + """Return the article image, or the category image if unset.""" + return self.image if self.image else self.category.image def get_absolute_url(self): - kwargs = { - 'year': self.date_created.strftime('%Y'), - 'month': self.date_created.strftime('%m'), - 'slug': self.slug, - } - return reverse('show-article', kwargs=kwargs) + """return the absolute URL for this article.""" + return reverse('show-article', + kwargs={'year': self.date_created.strftime('%Y'), + 'month': self.date_created.strftime('%m'), + 'slug': self.slug}) @property def headline(self): - headline = getattr(self, "headline_%s" % get_language()) - if not headline: - return mark_safe(self.headline_de) - else: - return mark_safe(headline) + """Return the localized headline, fallback to german if necessary.""" + return mark_safe( + getattr(self, "headline_%s" % get_language(), self.headline_de) + ) @property def content(self): - content = getattr(self, "content_%s" % get_language(), self.content_de) - if not content: - return mark_safe(self.content_de) - else: - return mark_safe(content) + """Return the localized content, fallback to german if necessary.""" + return mark_safe( + getattr(self, "content_%s" % get_language(), self.content_de) + ) + + +class Category(models.Model): + """A news category, articles will be assicuated with it.""" + name_de = models.CharField(_('Name'), max_length=80) + name_en = models.CharField(_('Name'), max_length=80, blank=True) + description_de = models.TextField(_('Description')) + description_en = models.TextField(_('Description'), blank=True) + image = models.ImageField(_('Image'), upload_to='news/categories/', + blank=True, null=True) + slug = models.SlugField(_('Slug'), unique=True, db_index=True) + + class Meta(object): + """Set default the ordering.""" + ordering = ('slug',) + verbose_name = _('Category') + verbose_name_plural = _('Categories') + + @property + def name(self): + """Return the localized name, fallback to german if necessary.""" + return getattr(self, "name_%s" % get_language(), self.name_de) + + @property + def description(self): + """Return the localized description, fallback to german if necessary.""" + return getattr(self, "description_%s" % get_language(), + self.description_de) + + def get_absolute_url(self): + """Return the URL of the article archive, filtered on this category.""" + return reverse('article-archive', kwargs={'category': self.slug}) + + def __str__(self): + """Return the localized name, fallback to german if necessary.""" + return self.name class Page(models.Model): - """ - Eine Seite auf der Homepage. Sie kann eine "statische" HTML Seite, - die URL einer dynamische Django View, oder ein PDF Dokument sein. - Jede Seite kann neben Deutsch auch auf Englisch angeboten werden. - Ist keine englische Übersetzung vorhanden, wird die deutsche Version - angeboten. - """ + """A page on this homepage. It can have a "static" HTML page, the URL of a + dynamic Django view, or a PDF document. + + Each page can be offered in German and English. If no English translation + is available, the German version will be displayed.""" menu_name_de = models.CharField( max_length=255, @@ -180,8 +229,8 @@ class Page(models.Model): content_type = models.IntegerField( choices=CONTENT_CHOICES, verbose_name=_('content type')) - content_de = RichTextField('Inhalt', blank=True) - content_en = RichTextField('Content', blank=True) + content_de = RichTextUploadingField('Inhalt', blank=True) + content_en = RichTextUploadingField('Content', blank=True) enable_comments = models.BooleanField( default=True, verbose_name=_('enable comments') @@ -206,43 +255,46 @@ class Page(models.Model): ) def __str__(self): - return u'%s' % self.title + """Return the localized title, fallback to german if necessary.""" + return self.title @property def content(self): - cont = getattr(self, "content_%s" % get_language()) or self.content_de - return mark_safe(cont) + """Return the localized content, fallback to german if necessary.""" + return mark_safe( + getattr(self, "content_%s" % get_language(), self.content_de) + ) @property def css_class(self): + """Returns the name of the content typ of this page as a CSS class. + This allows easy styling of page links, depending on the content type. + """ return CONTENT_CHOICES[self.content_type][1].lower().replace(' ', '_') @property def description(self): - lang_attr = "description_%s" % get_language() - return getattr(self, lang_attr, self.description_de) + """Return the localized description, fallback to german if necessary.""" + return getattr(self, "description_%s" % get_language(), + self.description_de) @property def menu_name(self): - lang_attr = "menu_name_%s" % get_language() - return getattr(self, lang_attr, self.menu_name_de) + """Return the localized menu name, fallback to german if necessary.""" + return getattr(self, "menu_name_%s" % get_language(), self.menu_name_de) @property def pdf_file(self): - lang_attr = "pdf_%s" % get_language() - return getattr(self, lang_attr, self.pdf_de) + """Return the localized PDF file, fallback to german if necessary.""" + return getattr(self, "pdf_%s" % get_language(), self.pdf_de) @property def title(self): - """ Return the translated title if available """ - lang_attr = "title_%s" % get_language() - return getattr(self, lang_attr, self.title_de) + """Return the localized title, fallback to german if necessary.""" + return getattr(self, "title_%s" % get_language(), self.title_de) def clean(self): - """ - - :return: - """ + """set the URL path, the right content type, and scrub the HTML code.""" if self.parent is None: self.path = self.slug else: @@ -251,7 +303,7 @@ class Page(models.Model): if self.content_type is None: if self.pdf_de: self.content_type = 2 - if self.content_de: + elif self.content_de: self.content_type = 1 else: self.content_type = 0 @@ -263,53 +315,23 @@ class Page(models.Model): _(u'Please upload a PDF-File to this PDF-Page.')) def get_absolute_url(self): - aboslute_url = '/' + self.path - if self.content_type == 1: - aboslute_url += '.html' - elif self.content_type == 2: - aboslute_url += '.pdf' - else: - aboslute_url += '/' - return aboslute_url + """Return the path with an extension that matches the content type. + It's useful for an user to match the URL to the contenttype. + + :return: sting with the absolute URL of this page.""" + return '/' + self.path + CONTENT_TYPE_EXTENSIONS[self.content_type] class Meta(object): + """Set default ordering and an unique priamry key to avoid dupes.""" ordering = ['parent__id', 'position'] unique_together = (('slug', 'parent'),) verbose_name = _('Page') verbose_name_plural = _('Pages') -class Category(models.Model): - name_de = models.CharField(_('Name'), max_length=80) - name_en = models.CharField(_('Name'), max_length=80, blank=True) - description_de = models.TextField(_('Description')) - description_en = models.TextField(_('Description'), blank=True) - image = models.ImageField(_('Image'), upload_to='news/categories/', - blank=True, null=True) - slug = models.SlugField(_('Slug'), unique=True, db_index=True) - - class Meta(object): - ordering = ('slug',) - verbose_name = _('Category') - verbose_name_plural = _('Categories') - - @property - def name(self): - return getattr(self, "name_%s" % get_language(), self.name_de) - - @property - def description(self): - return getattr(self, "description_%s" % get_language(), - self.description_de) - - def get_absolute_url(self): - return reverse('article-archive', kwargs={'category': self.slug}) - - def __str__(self): - return self.name - - -def force_cache_update(sender, instance, **kwargs): +def force_cache_update(sender, instance, **kwargs): # Ignore PyLintBear (W0613) + """A Django signal to trigger the save() method on all subpages to catch + possible URL changes and invalidate the cache.""" for page in instance.subpages.all(): page.clean() page.save() diff --git a/src/content/sitemaps.py b/src/content/sitemaps.py index aa85e66..472ee49 100644 --- a/src/content/sitemaps.py +++ b/src/content/sitemaps.py @@ -1,24 +1,31 @@ +"""Add News and static pages to the global sitemap.""" from django.contrib.sitemaps import Sitemap from .models import Article, Page class ArticleSitemap(Sitemap): + """Add the news artivles to the Sitemap""" changefreq = "never" priority = 0.6 def items(self): + """only add published articles to the sitemap""" return Article.objects.published() def lastmod(self, article): + """return the last modification date of the article.""" return article.date_modified class PageSitemap(Sitemap): + """add the static pages to the sitemap.""" changefreq = "monthly" priority = 0.4 def items(self): - return Page.objects.all() # filter(status__gt=0) + """add all pages to the sitemap.""" + return Page.objects.all() def lastmod(self, page): + """return the last modification date of the page.""" return page.date_modified diff --git a/src/content/static/js/ckeditor/CHANGES.md b/src/content/static/js/ckeditor/CHANGES.md deleted file mode 100644 index 72c5798..0000000 --- a/src/content/static/js/ckeditor/CHANGES.md +++ /dev/null @@ -1,1219 +0,0 @@ -CKEditor 4 Changelog -==================== - -## CKEditor 4.6.2 - -New Features: - -* [#16733](http://dev.ckeditor.com/ticket/16733): Added a new pastel color palette for the [Color Button](http://ckeditor.com/addon/colorbutton) plugin and a new [`config.colorButton_colorsPerRow`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-colorButton_colorsPerRow) configuration option for setting the number of rows in the color selector. -* [#16752](http://dev.ckeditor.com/ticket/16752): Added a new Azerbaijani localization. Thanks to the [Azerbaijani language team](https://www.transifex.com/ckeditor/teams/11143/az/)! -* [#13818](http://dev.ckeditor.com/ticket/13818): It is now possible to group [Widget](http://ckeditor.com/addon/widget) [style definitions](http://docs.ckeditor.com/#!/guide/dev_styles-section-widget-styles), so applying one style disables the other. - -Fixed Issues: - -* [#13446](http://dev.ckeditor.com/ticket/13446): [Chrome] Fixed: It is possible to type in an unfocused inline editor. -* [#14856](http://dev.ckeditor.com/ticket/14856): Fixed: [Font size and font family](http://ckeditor.com/addon/font) reset each other when modified at certain positions. -* [#16745](http://dev.ckeditor.com/ticket/16745): [Edge] Fixed: List items are lost when [pasted from Word](http://ckeditor.com/addon/pastefromword). -* [#16682](http://dev.ckeditor.com/ticket/16682): [Edge] Fixed: A list gets [pasted from Word](http://ckeditor.com/addon/pastefromword) as a set of paragraphs. Added the [`config.pasteFromWord_heuristicsEdgeList`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWord_heuristicsEdgeList) configuration option. -* [#10373](http://dev.ckeditor.com/ticket/10373): Fixed: Context menu items can be dragged into the editor. -* [#16728](http://dev.ckeditor.com/ticket/16728): [IE] Fixed: [Copy Formatting](http://ckeditor.com/addon/copyformatting) breaks the editor in Quirks Mode. -* [#16795](http://dev.ckeditor.com/ticket/16795): [IE] Fixed: [Copy Formatting](http://ckeditor.com/addon/copyformatting) breaks the editor in Compatibility Mode. -* [#16675](http://dev.ckeditor.com/ticket/16675): Fixed: Styles applied with [Copy Formatting](http://ckeditor.com/addon/copyformatting) to a single table cell are applied to the whole table. -* [#16753](http://dev.ckeditor.com/ticket/16753): Fixed: [`element.setSize`](http://docs.ckeditor.com/#!/api/CKEDITOR.dom.element-method-setSize) sets incorrect editor dimensions if the border width is represented as a fraction of pixels. -* [#16705](http://dev.ckeditor.com/ticket/16705): [Firefox] Fixed: Unable to paste images as Base64 strings when using [Clipboard](http://ckeditor.com/addon/clipboard). -* [#14869](http://dev.ckeditor.com/ticket/14869): Fixed: JavaScript error is thrown when trying to use [Find](http://ckeditor.com/addon/find) in a [`
`-based editor](http://ckeditor.com/addon/divarea). - -## CKEditor 4.6.1 - -New Features: - -* [#16639](http://dev.ckeditor.com/ticket/16639): The `callback` parameter in the [CKEDITOR.ajax.post](http://docs.ckeditor.com/#!/api/CKEDITOR.ajax-method-post) method became optional. - -Fixed Issues: - -* [#11064](http://dev.ckeditor.com/ticket/11064): [Blink, WebKit] Fixed: Cannot select all editor content when a widget or a non-editable element is the first or last element of the content. Also fixes this issue in the [Select All](http://ckeditor.com/addon/selectall) plugin. -* [#14755](http://dev.ckeditor.com/ticket/14755): [Blink, WebKit, IE8] Fixed: Browser hangs when a table is inserted in the place of a selected list with an empty last item. -* [#16624](http://dev.ckeditor.com/ticket/16624): Fixed: Improved the [Color Button](http://ckeditor.com/addon/colorbutton) plugin which will now normalize the CSS `background` property if it only contains a color value. This fixes missing background colors when using [Paste from Word](http://ckeditor.com/addon/pastefromword). -* [#16600](http://dev.ckeditor.com/ticket/16600): [Blink, WebKit] Fixed: Error thrown occasionally by an uninitialized editable for multiple CKEditor instances on the same page. - -## CKEditor 4.6 - -New Features: - -* [#14569](http://dev.ckeditor.com/ticket/14569): Added a new, flat, default CKEditor skin called [Moono-Lisa](http://ckeditor.com/addon/moono-lisa). Refreshed default colors available in the [Color Button](http://ckeditor.com/addon/colorbutton) plugin ([Text Color and Background Color](http://docs.ckeditor.com/#!/guide/dev_colorbutton) feature). -* [#14707](http://dev.ckeditor.com/ticket/14707): Added a new [Copy Formatting](http://ckeditor.com/addon/copyformatting) feature to enable easy copying of styles between your document parts. -* Introduced the completely rewritten [Paste from Word](http://ckeditor.com/addon/pastefromword) plugin: - * Backward incompatibility: The [`config.pasteFromWordRemoveFontStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveFontStyles) option now defaults to `false`. This option will be deprecated in the future. Use [Advanced Content Filter](http://docs.ckeditor.com/#!/guide/dev_acf) to replicate the effect of setting it to `true`. - * Backward incompatibility: The [`config.pasteFromWordNumberedHeadingToList`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordNumberedHeadingToList) and [`config.pasteFromWordRemoveStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveStyles) options were dropped and no longer have any effect on pasted content. - * Major improvements in preservation of list numbering, styling and indentation (nested lists with multiple levels). - * Major improvements in document structure parsing that fix plenty of issues with distorted or missing content after paste. -* Added new translation: Occitan. Thanks to [Cédric Valmary](https://totenoc.eu/)! -* [#10015](http://dev.ckeditor.com/ticket/10015): Keyboard shortcuts (relevant to the operating system in use) will now be displayed in tooltips and context menus. -* [#13794](http://dev.ckeditor.com/ticket/13794): The [Upload Image](http://ckeditor.com/addon/uploadimage) feature now uses `uploaded.width/height` if set. -* [#12541](http://dev.ckeditor.com/ticket/12541): Added the [Upload File](http://ckeditor.com/addon/uploadfile) plugin that lets you upload a file by drag&dropping it into the editor content. -* [#14449](http://dev.ckeditor.com/ticket/14449): Introduced the [Balloon Panel](http://ckeditor.com/addon/balloonpanel) plugin that lets you create stylish floating UI elements for the editor. -* [#12077](https://dev.ckeditor.com/ticket/12077): Added support for the HTML5 `download` attribute in link (``) elements. Selecting the "Force Download" checkbox in the [Link](http://ckeditor.com/addon/link) dialog will cause the linked file to be downloaded automatically. Thanks to [sbusse](https://github.com/sbusse)! -* [#13518](http://dev.ckeditor.com/ticket/13518): Introduced the [`additionalRequestParameters`](http://docs.ckeditor.com/#!/api/CKEDITOR.fileTools.uploadWidgetDefinition-property-additionalRequestParameters) property for file uploads to make it possible to send additional information about the uploaded file to the server. -* [#14889](http://dev.ckeditor.com/ticket/14889): Added the [`config.image2_altRequired`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-image2_altRequired) option for the [Enhanced Image](http://ckeditor.com/addon/image2) plugin to allow making alternative text a mandatory field. Thanks to [Andrey Fedoseev](https://github.com/andreyfedoseev)! - -Fixed Issues: - -* [#9991](http://dev.ckeditor.com/ticket/9991): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) should only normalize input data. -* [#7209](http://dev.ckeditor.com/ticket/7209): Fixed: Lists with 3 levels not [pasted from Word](http://ckeditor.com/addon/pastefromword) correctly. -* [#14335](http://dev.ckeditor.com/ticket/14335): Fixed: Pasting a numbered list starting with a value different from "1" from Microsoft Word does not work correctly. -* [#14542](http://dev.ckeditor.com/ticket/14542): Fixed: Copying a numbered list from Microsoft Word does not preserve list formatting. -* [#14544](http://dev.ckeditor.com/ticket/14544): Fixed: Copying a nested list from Microsoft Word results in an empty list. -* [#14660](http://dev.ckeditor.com/ticket/14660): Fixed: [Pasting text from Word](http://ckeditor.com/addon/pastefromword) breaks the styling in some cases. -* [#14867](http://dev.ckeditor.com/ticket/14867): [Firefox] Fixed: Text gets stripped when [pasting content from Word](http://ckeditor.com/addon/pastefromword). -* [#2507](http://dev.ckeditor.com/ticket/2507): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) does not detect pasting a part of a paragraph. -* [#3336](http://dev.ckeditor.com/ticket/3336): Fixed: Extra blank row added on top of the content [pasted from Word](http://ckeditor.com/addon/pastefromword). -* [#6115](http://dev.ckeditor.com/ticket/6115): Fixed: When Right-to-Left text direction is applied to a table [pasted from Word](http://ckeditor.com/addon/pastefromword), borders are missing on one side. -* [#6342](http://dev.ckeditor.com/ticket/6342): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) filters out a basic text style when it is [configured to use attributes](http://docs.ckeditor.com/#!/guide/dev_basicstyles-section-custom-basic-text-style-definition). -* [#6457](http://dev.ckeditor.com/ticket/6457): [IE] Fixed: [Pasting from Word](http://ckeditor.com/addon/pastefromword) is extremely slow. -* [#6789](http://dev.ckeditor.com/ticket/6789): Fixed: The `mso-list: ignore` style is not handled properly when [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#7262](http://dev.ckeditor.com/ticket/7262): Fixed: Lists in preformatted body disappear when [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#7662](http://dev.ckeditor.com/ticket/7662): [Opera] Fixed: Extra empty number/bullet shown in the editor body when editing a multi-level list [pasted from Word](http://ckeditor.com/addon/pastefromword). -* [#7807](http://dev.ckeditor.com/ticket/7807): Fixed: Last item in a list not converted to a `
  • ` element after [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#7950](http://dev.ckeditor.com/ticket/7950): [IE] Fixed: Content [from Word pasted](http://ckeditor.com/addon/pastefromword) differently than in other browsers. -* [#7982](http://dev.ckeditor.com/ticket/7982): Fixed: Multi-level lists get split into smaller ones when [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#8231](http://dev.ckeditor.com/ticket/8231): [WebKit, Opera] Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) inserts empty paragraphs. -* [#8266](http://dev.ckeditor.com/ticket/8266): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) inserts a blank line at the top. -* [#8341](http://dev.ckeditor.com/ticket/8341), [#7646](http://dev.ckeditor.com/ticket/7646): Fixed: Faulty removal of empty `` elements in [Paste from Word](http://ckeditor.com/addon/pastefromword) content cleanup breaking content formatting. -* [#8754](http://dev.ckeditor.com/ticket/8754): [Firefox] Fixed: Incorrect pasting of multiple nested lists in [Paste from Word](http://ckeditor.com/addon/pastefromword). -* [#8983](http://dev.ckeditor.com/ticket/8983): Fixed: Alignment lost when [pasting from Word](http://ckeditor.com/addon/pastefromword) with [`config.enterMode`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-enterMode) set to [`CKEDITOR.ENTER_BR`](http://docs.ckeditor.com/#!/api/CKEDITOR-property-ENTER_BR). -* [#9331](http://dev.ckeditor.com/ticket/9331): [IE] Fixed: [Pasting text from Word](http://ckeditor.com/addon/pastefromword) creates a simple Caesar cipher. -* [#9422](http://dev.ckeditor.com/ticket/9422): Fixed: [Paste from Word](http://ckeditor.com/addon/pastefromword) leaves an unwanted `color:windowtext` style. -* [#10011](http://dev.ckeditor.com/ticket/10011): [IE9-10] Fixed: [`config.pasteFromWordRemoveFontStyles`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-pasteFromWordRemoveFontStyles) is ignored under certain conditions. -* [#10643](http://dev.ckeditor.com/ticket/10643): Fixed: Differences between using Ctrl+V and pasting from the [Paste from Word](http://ckeditor.com/addon/pastefromword) dialog. -* [#10784](http://dev.ckeditor.com/ticket/10784): Fixed: Lines missing when [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#11294](http://dev.ckeditor.com/ticket/11294): [IE10] Fixed: Font size is not preserved when [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#11627](http://dev.ckeditor.com/ticket/11627): Fixed: Missing words when [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#12784](http://dev.ckeditor.com/ticket/12784): Fixed: Bulleted list with custom bullets gets changed to a numbered list when [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#13174](http://dev.ckeditor.com/ticket/13174): Fixed: Data loss after [pasting from Word](http://ckeditor.com/addon/pastefromword). -* [#13828](http://dev.ckeditor.com/ticket/13828): Fixed: Widget classes should be added to the wrapper rather than the widget element. -* [#13829](http://dev.ckeditor.com/ticket/13829): Fixed: No class in [Widget](http://ckeditor.com/addon/widget) wrapper to identify the widget type. -* [#13519](http://dev.ckeditor.com/ticket/13519): Server response received when uploading files should be more flexible. - -Other Changes: - -* Updated [SCAYT](http://ckeditor.com/addon/scayt) (Spell Check As You Type) and [WebSpellChecker](http://ckeditor.com/addon/wsc) plugins: - * Support for the new default Moono-Lisa skin. - * [#121](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/121): Fixed: [Basic Styles](http://ckeditor.com/addon/basicstyles) do not work when SCAYT is enabled. - * [#125](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/125): Fixed: Inline styles are not continued when writing multiple lines of styled text with SCAYT enabled. - * [#127](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/127): Fixed: Uncaught TypeError after enabling SCAYT in the CKEditor `
    ` element. - * [#128](https://github.com/WebSpellChecker/ckeditor-plugin-scayt/issues/128): Fixed: Error thrown after enabling SCAYT caused by conflicts with RequireJS. - -## CKEditor 4.5.11 - -**Security Updates:** - -* [Severity: minor] Fixed the `target="_blank"` vulnerability reported by James Gaskell. - - Issue summary: If a victim had access to a spoofed version of ckeditor.com via HTTP (e.g. due to DNS spoofing, using a hacked public network or mailicious hotspot), then when using a link to the ckeditor.com website it was possible for the attacker to change the current URL of the opening page, even if the opening page was protected with SSL. - - An upgrade is recommended. - -New Features: - -* [#14747](http://dev.ckeditor.com/ticket/14747): The [Enhanced Image](http://ckeditor.com/addon/image2) caption now supports the link `target` attribute. -* [#7154](http://dev.ckeditor.com/ticket/7154): Added support for the "Display Text" field to the [Link](http://ckeditor.com/addon/link) dialog. Thanks to [Ryan Guill](https://github.com/ryanguill)! - -Fixed Issues: - -* [#13362](http://dev.ckeditor.com/ticket/13362): [Blink, WebKit] Fixed: Active widget element is not cached when it is losing focus and it is inside an editable element. -* [#13755](http://dev.ckeditor.com/ticket/13755): [Edge] Fixed: Pasting images does not work. -* [#13548](http://dev.ckeditor.com/ticket/13548): [IE] Fixed: Clicking the [elements path](http://ckeditor.com/addon/elementspath) disables Cut and Copy icons. -* [#13812](http://dev.ckeditor.com/ticket/13812): Fixed: When aborting file upload the placeholder for image is left. -* [#14659](http://dev.ckeditor.com/ticket/14659): [Blink] Fixed: Content scrolled to the top after closing the dialog in a [`
    `-based editor](http://ckeditor.com/addon/divarea). -* [#14825](http://dev.ckeditor.com/ticket/14825): [Edge] Fixed: Focusing the editor causes unwanted scrolling due to dropped support for the `setActive` method. - -## CKEditor 4.5.10 - -Fixed Issues: - -* [#10750](http://dev.ckeditor.com/ticket/10750): Fixed: The editor does not escape the `font-style` family property correctly, removing quotes and whitespace from font names. -* [#14413](http://dev.ckeditor.com/ticket/14413): Fixed: The [Auto Grow](http://ckeditor.com/addon/autogrow) plugin with the [`config.autoGrow_onStartup`](http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-autoGrow_onStartup) option set to `true` does not work properly for an editor that is not visible. -* [#14451](http://dev.ckeditor.com/ticket/14451): Fixed: Numeric element ID not escaped properly. Thanks to [Jakub Chalupa](https://github.com/chaluja7)! -* [#14590](http://dev.ckeditor.com/ticket/14590): Fixed: Additional line break appearing after inline elements when switching modes. Thanks to [dpidcock](https://github.com/dpidcock)! -* [#14539](https://dev.ckeditor.com/ticket/14539): Fixed: JAWS reads "selected Blank" instead of "selected " when selecting a widget. -* [#14701](http://dev.ckeditor.com/ticket/14701): Fixed: More precise labels for [Enhanced Image](http://ckeditor.com/addon/image2) and [Placeholder](http://ckeditor.com/addon/placeholder) widgets. -* [#14667](http://dev.ckeditor.com/ticket/14667): [IE] Fixed: Removing background color from selected text removes background color from the whole paragraph. -* [#14252](http://dev.ckeditor.com/ticket/14252): [IE] Fixed: Styles drop-down list does not always reflect the current style of the text line. -* [#14275](http://dev.ckeditor.com/ticket/14275): [IE9+] Fixed: `onerror` and `onload` events are not used in browsers it could have been used when loading scripts dynamically. - -## CKEditor 4.5.9 - -Fixed Issues: - -* [#10685](http://dev.ckeditor.com/ticket/10685): Fixed: Unreadable toolbar icons after updating to the new editor version. Fixed with [6876179](https://github.com/ckeditor/ckeditor-dev/commit/6876179db4ee97e786b07b8fd72e6b4120732185) in [ckeditor-dev](https://github.com/ckeditor/ckeditor-dev) and [6c9189f4](https://github.com/ckeditor/ckeditor-presets/commit/6c9189f46392d2c126854fe8889b820b8c76d291) in [ckeditor-presets](https://github.com/ckeditor/ckeditor-presets). -* [#14573](https://dev.ckeditor.com/ticket/14573): Fixed: Missing [Widget](http://ckeditor.com/addon/widget) drag handler CSS when there are multiple editor instances. -* [#14620](https://dev.ckeditor.com/ticket/14620): Fixed: Setting both the `min-height` style for the `` element and the `height` style for the `` element breaks the [Auto Grow](http://ckeditor.com/addon/autogrow) plugin. -* [#14538](http://dev.ckeditor.com/ticket/14538): Fixed: Keyboard focus goes into an embedded `