def entries(self): """ Return the entries that are published under this node. """ # Since there is currently no filtering in place, return all entries. EntryModel = get_entry_model() qs = get_entry_model().objects.order_by('-publication_date') # Only limit to current language when this makes sense. if issubclass(EntryModel, TranslatableModel): admin_form_language = self.get_current_language() # page object is in current language tab. qs = qs.active_translations(admin_form_language).language(admin_form_language) return qs
def get_queryset(self): # NOTE: This is also workaround, defining the queryset static somehow caused results to remain cached. qs = get_entry_model().objects.filter(parent_site=self.request.site) qs = qs.published() if self.prefetch_translations: qs = qs.prefetch_related('translations') return qs
def get_object(self, queryset=None): if issubclass(get_entry_model(), TranslatableModel): # Filter by slug and language return self._translated_get_object(queryset) else: # Regular slug check return super(BaseDetailMixin, self).get_object(queryset)
class GetEntriesNode(BlogAssignmentOrInclusionNode): """ Query the entries in the database, and render them. This template tag supports the following syntax: .. code-block:: html+django {% get_entries category='slug' year=2012 as entries %} {% for entry in entries %}...{% endfor %} {% get_entries category='slug' year=2012 template="name/of/template.html" %} The allowed query parameters are: * ``category``: The slug or ID of a category * ``tag``: The slug or ID of a tag * ``author``: The username or ID of an author * ``year``: The full year. * ``month``: The month number to display * ``day``: The day of the month to display. * ``order``: Which field to order on, this can be: * ``slug``: The URL name of the entry. * ``title``: The title of the entry. * ``author``: The author full name * ``author_slug``: The author URL name. * ``category``: The category name. * ``category_slug``: The category URL name. * ``tag``: The tag name * ``tag_slug``: The tag URL name. * ``date``: The publication date of the entry. * ``orderby``: can be ASC/ascending or DESC/descending. The default depends on the ``order`` field. * ``limit``: The maximum number of entries to return. """ template_name = "fluent_blogs/templatetags/entries.html" context_value_name = 'entries' allowed_kwargs = ( 'category', 'tag', 'author', 'year', 'month', 'day', 'orderby', 'order', 'limit', 'parent_site', ) model = get_entry_model() def get_value(self, context, *tag_args, **tag_kwargs): # Query happens in the backend, # the templatetag is considered to be a frontend. qs = self.model.objects.all() qs = query_entries(qs, **tag_kwargs) return qs
def get_object(self, queryset=None): if issubclass(get_entry_model(), TranslatableModel): # Filter by slug and language # Note that translation support is still optional, # even though the class inheritance includes it. return TranslatableSlugMixin.get_object(self, queryset) else: # Regular slug check, skip TranslatableSlugMixin return SingleObjectMixin.get_object(self, queryset)
def description_template(self): EntryModel = get_entry_model() templates = [ "{0}/{1}_feed_description.html".format(EntryModel._meta.app_label, EntryModel._meta.object_name.lower()), "fluent_blogs/entry_feed_description.html", # New name "fluent_blogs/feeds/entry/description.html" # Old name ] # The value is passed to get_template by the Feed class, so reduce the list here manually. for name in templates: try: get_template(name) except TemplateDoesNotExist: pass else: setattr(self.__class__, 'description_template', name) return templates return None
def get_queryset(self): # The DetailView redefines get_queryset() to show detail pages for staff members. # All other overviews won't show the draft pages yet. qs = get_entry_model().objects.published(for_user=self.request.user) if self.prefetch_translations: qs = qs.prefetch_related('translations') # Allow same slug in different dates # The available arguments depend on the FLUENT_BLOGS_ENTRY_LINK_STYLE setting. year = int(self.kwargs['year']) if 'year' in self.kwargs else None month = int(self.kwargs['month']) if 'month' in self.kwargs else None day = int(self.kwargs['day']) if 'day' in self.kwargs else None range = get_date_range(year, month, day) if range: qs = qs.filter(publication_date__range=range) return qs
def get_queryset(self): # The DetailView redefines get_queryset() to show detail pages for staff members. # All other overviews won't show the draft pages yet. qs = get_entry_model().objects.published(for_user=self.request.user) if self.prefetch_translations: qs = qs.prefetch_related("translations") # Allow same slug in different dates # The available arguments depend on the FLUENT_BLOGS_ENTRY_LINK_STYLE setting. year = int(self.kwargs["year"]) if "year" in self.kwargs else None month = int(self.kwargs["month"]) if "month" in self.kwargs else None day = int(self.kwargs["day"]) if "day" in self.kwargs else None range = get_date_range(year, month, day) if range: qs = qs.filter(publication_date__range=range) return qs
from django.contrib.admin import widgets from fluent_blogs.admin.abstractbase import AbstractEntryBaseAdmin, AbstractTranslatableEntryBaseAdmin, SeoEntryAdminMixin from fluent_blogs.models import get_entry_model, Entry_Translation from parler.models import TranslatableModel EntryModel = get_entry_model() _model_fields = EntryModel._meta.get_all_field_names() if issubclass(EntryModel, TranslatableModel): _entry_admin_base = AbstractTranslatableEntryBaseAdmin _model_fields += Entry_Translation.get_translated_fields() else: _entry_admin_base = AbstractEntryBaseAdmin class EntryAdmin(SeoEntryAdminMixin, _entry_admin_base): """ The Django admin class for the default blog :class:`~fluent_blogs.models.Entry` model. When using a custom model, you can use :class:`AbstractEntryBaseAdmin`, which isn't attached to any of the optional fields. """ # Redefine the fieldset, because it will be extended with auto-detected fields. FIELDSET_GENERAL = (None, { 'fields': ('title', 'slug', 'status',), # is filled with ('intro', 'contents', 'categories', 'tags', 'enable_comments') below }) # For Django 1.4, the fieldsets shouldn't be declared with 'fieldsets =' # as the admin validation won't recognize the translated fields. # The 1.4 validation didn't check the form at all, but only checks the model fields. # As of Django 1.5, using 'fieldsets = ..' with translated fields just works. declared_fieldsets = (
def test_get_entry_model(self): self.assertIs(get_entry_model(), Entry)
def get_queryset(self): # NOTE: This is a workaround, defining the queryset static somehow caused results to remain cached. return get_entry_model().objects.published()
from django.core.exceptions import ImproperlyConfigured from django.urls import NoReverseMatch from django.utils.html import format_html from django.utils.timezone import now from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.safestring import mark_safe from fluent_blogs import appsettings from fluent_blogs.admin.forms import AbstractEntryBaseAdminForm, AbstractTranslatableEntryBaseAdminForm from fluent_blogs.base_models import AbstractEntryBase from fluent_blogs.models import get_entry_model from fluent_utils.dry.admin import MultiSiteAdminMixin from fluent_contents.admin import PlaceholderFieldAdmin from parler.admin import TranslatableAdmin from parler.models import TranslationDoesNotExist EntryModel = get_entry_model() class AbstractEntryBaseAdmin(MultiSiteAdminMixin, PlaceholderFieldAdmin): """ The base functionality of the admin, which only uses the fields of the :class:`~fluent_blogs.base_models.AbstractEntryBase` model. Everything else is branched off in the :class:`EntryAdmin` class. """ filter_site = appsettings.FLUENT_BLOGS_FILTER_SITE_ID list_display = ('title', 'status_column', 'modification_date', 'actions_column') list_filter = ('status', ) date_hierarchy = 'publication_date' search_fields = ('slug', 'title') actions = ['make_published']
def blog_entry_admin_change_url(entry): model = get_entry_model() return reverse('admin:{0}_{1}_change'.format(model._meta.app_label, model._meta.module_name), args=(entry.pk,))
def handle(self, *args, **options): if args: raise CommandError("Command doesn't accept any arguments") Entry = get_entry_model() CategoryM2M = Entry.categories.through old_fk = CategoryM2M._meta.get_field('category') CurrentModel = old_fk.remote_field.model self.stdout.write("Current Entry.categories model: <{0}.{1}>".format( CurrentModel._meta.app_label, CurrentModel._meta.object_name )) old = options['from'] new = options['to'] if not old or not new: raise CommandError("Expected --from and --to options") if old.lower() == 'categories.category' and 'categories' not in settings.INSTALLED_APPS: # Can't import it in a Django 1.8+ project. OldModel = DummyCategoryBase else: try: OldModel = apps.get_model(old) except LookupError as e: raise CommandError("Invalid --from value: {0}".format(e)) if not issubclass(OldModel, MPTTModel): raise CommandError("Expected MPTT model for --from value") try: NewModel = apps.get_model(new) except LookupError as e: raise CommandError("Invalid --to value: {0}".format(e)) if not issubclass(NewModel, MPTTModel): raise CommandError("Expected MPTT model for --to value") if NewModel.objects.all().exists(): raise CommandError("New model already has records, it should be an empty table!") old_i18n = issubclass(OldModel, TranslatableModel) new_i18n = issubclass(NewModel, TranslatableModel) old_title = _detect_title_field(OldModel) new_title = _detect_title_field(NewModel) mptt_fields = "lft, rght, tree_id, level, parent_id" with transaction.atomic(): if not old_i18n and not new_i18n: # Untranslated to untranslated self.stdout.write("* Copying category fields...") with connection.cursor() as cursor: cursor.execute( 'INSERT INTO {new_model}(id, slug, {new_title}, {mptt_fields}})' ' SELECT id, slug, {old_title}, {mptt_fields} FROM {old_model}'.format( new_model=NewModel._meta.db_table, new_title=new_title, old_model=OldModel._meta.db_table, old_title=old_title, mptt_fields=mptt_fields, )) elif not old_i18n and new_i18n: # Untranslated to translated # - base table fields with connection.cursor() as cursor: self.stdout.write("* Copying category base fields...") cursor.execute( 'INSERT INTO {new_model}(id, {mptt_fields})' ' SELECT id, {mptt_fields} FROM {old_model}'.format( new_model=NewModel._meta.db_table, old_model=OldModel._meta.db_table, mptt_fields=mptt_fields, )) # - create translations on fallback language self.stdout.write("* Creating category translations...") cursor.execute( 'INSERT INTO {new_translations}(master_id, language_code, slug, {new_title})' ' SELECT id, %s, slug, {old_title} FROM {old_model}'.format( new_translations=NewModel._parler_meta.root_model._meta.db_table, new_title=new_title, old_model=OldModel._meta.db_table, old_title=old_title, ), [appsettings.FLUENT_BLOGS_DEFAULT_LANGUAGE_CODE]) elif old_i18n and not new_i18n: # Reverse, translated to untranslated. Take fallback only # Convert all fields back to the single-language table. self.stdout.write("* Copying category fields and fallback language fields...") for old_category in OldModel.objects.all(): translations = old_category.translations.all() try: # Try default translation old_translation = translations.get(language_code=appsettings.FLUENT_BLOGS_DEFAULT_LANGUAGE_CODE) except ObjectDoesNotExist: try: # Try internal fallback old_translation = translations.get(language_code__in=('en-us', 'en')) except ObjectDoesNotExist: # Hope there is a single translation old_translation = translations.get() fields = dict( id=old_category.id, lft=old_category.lft, rght=old_category.rght, tree_id=old_category.tree_id, level=old_category.level, # parler fields _language_code=old_translation.language_code, slug=old_category.slug ) fields[new_title] = getattr(old_translation, old_title) NewModel.objects.create(**fields) elif old_i18n and new_i18n: # Translated to translated # - base table with connection.cursor() as cursor: self.stdout.write("* Copying category base fields...") cursor.execute( 'INSERT INTO {new_model}(id, {mptt_fields})' ' SELECT id, {mptt_fields} FROM {old_model}'.format( new_model=NewModel._meta.db_table, old_model=OldModel._meta.db_table, mptt_fields=mptt_fields, )) # - all translations self.stdout.write("* Copying category translations...") cursor.execute( 'INSERT INTO {new_translations}(master_id, language_code, slug, {new_title})' ' SELECT id, languag_code, slug, {old_title} FROM {old_translations}'.format( new_translations=NewModel._parler_meta.root_model._meta.db_table, new_title=new_title, old_translations=OldModel._parler_meta.root_model._meta.db_table, old_title=old_title, ), [appsettings.FLUENT_BLOGS_DEFAULT_LANGUAGE_CODE]) else: raise NotImplementedError() # impossible combination self.stdout.write("* Switching M2M foreign key constraints...") __, __, __, kwargs = old_fk.deconstruct() kwargs['to'] = NewModel new_fk = models.ForeignKey(**kwargs) new_fk.set_attributes_from_name(old_fk.name) with connection.schema_editor() as schema_editor: schema_editor.alter_field(CategoryM2M, old_fk, new_fk) self.stdout.write("Done.\n") self.stdout.write("You may now remove the old category app from your project, INSTALLED_APPS and database.\n")
def blog_entry_admin_change_url(entry): model = get_entry_model() return reverse('admin:{0}_{1}_change'.format(model._meta.app_label, model._meta.model_name), args=(entry.pk, ))
def get_entry_queryset(): # Avoid being cached at module level, always return a new queryset. return get_entry_model().objects.published().order_by('-publication_date')
def get_entry_queryset(): # Avoid being cached at module level, always return a new queryset. return get_entry_model().objects.published().active_translations( ).order_by('-publication_date')
def handle_noargs(self, **options): try: from django.apps import apps except ImportError: # Don't bother migrating old south tables, first migrate to Django 1.7 please. raise CommandError("This is a Django 1.7+ command only") Entry = get_entry_model() CategoryM2M = Entry.categories.through old_fk = CategoryM2M._meta.get_field('category') CurrentModel = old_fk.rel.to self.stdout.write("Current Entry.categories model: <{0}.{1}>".format( CurrentModel._meta.app_label, CurrentModel._meta.object_name )) old = options['from'] new = options['to'] if not old or not new: raise CommandError("Expected --from and --to options") if old.lower() == 'categories.category' and 'categories' not in settings.INSTALLED_APPS: # Can't import it in a Django 1.8+ project. OldModel = DummyCategoryBase else: try: OldModel = apps.get_model(old) except LookupError as e: raise CommandError("Invalid --from value: {0}".format(e)) if not issubclass(OldModel, MPTTModel): raise CommandError("Expected MPTT model for --from value") try: NewModel = apps.get_model(new) except LookupError as e: raise CommandError("Invalid --to value: {0}".format(e)) if not issubclass(NewModel, MPTTModel): raise CommandError("Expected MPTT model for --to value") if NewModel.objects.all().exists(): raise CommandError("New model already has records, it should be an empty table!") old_i18n = issubclass(OldModel, TranslatableModel) new_i18n = issubclass(NewModel, TranslatableModel) old_title = _detect_title_field(OldModel) new_title = _detect_title_field(NewModel) mptt_fields = "lft, rght, tree_id, level, parent_id" with transaction.atomic(): if not old_i18n and not new_i18n: # Untranslated to untranslated self.stdout.write("* Copying category fields...") with connection.cursor() as cursor: cursor.execute( 'INSERT INTO {new_model}(id, slug, {new_title}, {mptt_fields}})' ' SELECT id, slug, {old_title}, {mptt_fields} FROM {old_model}'.format( new_model=NewModel._meta.db_table, new_title=new_title, old_model=OldModel._meta.db_table, old_title=old_title, mptt_fields=mptt_fields, )) elif not old_i18n and new_i18n: # Untranslated to translated # - base table fields with connection.cursor() as cursor: self.stdout.write("* Copying category base fields...") cursor.execute( 'INSERT INTO {new_model}(id, {mptt_fields})' ' SELECT id, {mptt_fields} FROM {old_model}'.format( new_model=NewModel._meta.db_table, old_model=OldModel._meta.db_table, mptt_fields=mptt_fields, )) # - create translations on fallback language self.stdout.write("* Creating category translations...") cursor.execute( 'INSERT INTO {new_translations}(master_id, language_code, slug, {new_title})' ' SELECT id, %s, slug, {old_title} FROM {old_model}'.format( new_translations=NewModel._parler_meta.root_model._meta.db_table, new_title=new_title, old_model=OldModel._meta.db_table, old_title=old_title, ), [appsettings.FLUENT_BLOGS_DEFAULT_LANGUAGE_CODE]) elif old_i18n and not new_i18n: # Reverse, translated to untranslated. Take fallback only # Convert all fields back to the single-language table. self.stdout.write("* Copying category fields and fallback language fields...") for old_category in OldModel.objects.all(): translations = old_category.translations.all() try: # Try default translation old_translation = translations.get(language_code=appsettings.FLUENT_BLOGS_DEFAULT_LANGUAGE_CODE) except ObjectDoesNotExist: try: # Try internal fallback old_translation = translations.get(language_code__in=('en-us', 'en')) except ObjectDoesNotExist: # Hope there is a single translation old_translation = translations.get() fields = dict( id=old_category.id, lft=old_category.lft, rght=old_category.rght, tree_id=old_category.tree_id, level=old_category.level, # parler fields _language_code=old_translation.language_code, slug=old_category.slug ) fields[new_title] = getattr(old_translation, old_title) NewModel.objects.create(**fields) elif old_i18n and new_i18n: # Translated to translated # - base table with connection.cursor() as cursor: self.stdout.write("* Copying category base fields...") cursor.execute( 'INSERT INTO {new_model}(id, {mptt_fields})' ' SELECT id, {mptt_fields} FROM {old_model}'.format( new_model=NewModel._meta.db_table, old_model=OldModel._meta.db_table, mptt_fields=mptt_fields, )) # - all translations self.stdout.write("* Copying category translations...") cursor.execute( 'INSERT INTO {new_translations}(master_id, language_code, slug, {new_title})' ' SELECT id, languag_code, slug, {old_title} FROM {old_translations}'.format( new_translations=NewModel._parler_meta.root_model._meta.db_table, new_title=new_title, old_translations=OldModel._parler_meta.root_model._meta.db_table, old_title=old_title, ), [appsettings.FLUENT_BLOGS_DEFAULT_LANGUAGE_CODE]) else: raise NotImplementedError() # impossible combination self.stdout.write("* Switching M2M foreign key constraints...") __, __, __, kwargs = old_fk.deconstruct() kwargs['to'] = NewModel new_fk = models.ForeignKey(**kwargs) new_fk.set_attributes_from_name(old_fk.name) with connection.schema_editor() as schema_editor: schema_editor.alter_field(CategoryM2M, old_fk, new_fk) self.stdout.write("Done.\n") self.stdout.write("You may now remove the old category app from your project, INSTALLED_APPS and database.\n")
from django.contrib import admin from fluent_blogs.admin.entryadmin import AbstractEntryBaseAdminForm, AbstractTranslatableEntryBaseAdminForm, AbstractEntryBaseAdmin, AbstractTranslatableEntryBaseAdmin, SeoEntryAdminMixin, EntryAdmin from fluent_blogs.models import get_entry_model, Entry __all__ = ( 'AbstractEntryBaseAdminForm', 'AbstractTranslatableEntryBaseAdminForm', 'AbstractEntryBaseAdmin', 'AbstractTranslatableEntryBaseAdmin', 'SeoEntryAdminMixin', 'EntryAdmin', ) # Ony register the admin when the entry model is not customized. if get_entry_model() is Entry: admin.site.register(Entry, EntryAdmin)
def get_entry_queryset(site): # Avoid being cached at module level, always return a new queryset. return get_entry_model().objects.filter(parent_site=site).published().active_translations().order_by('-publication_date')