Skip to content

Commit

Permalink
WIP make content models swappable, closes stephenmcd#1160
Browse files Browse the repository at this point in the history
(It's marked as WIP because tests are far from passing, and we can
always discuss the coding style and naming.)

Any of the following models can now be swapped by your own version:

* Page
* RichTextPage
* Link
* BlogPost
* BlogCategory
* Form
* FormEntry
* Field
* FieldEntry
* Gallery
* GalleryImage

So you can keep the same features and API but add fields, methods, inherit from
other classes (geo models for geo fields, third-party models...), etc.
without having to resort to model inheritance.

Just make sure you inherit from each respective abstract base class.

Contrary to my first submission, I kept the BaseXXX classes for a
smoother transition path.

I didn't change imports or references in the admin modules, I followed
UserAdmin conventions here. You'll have to explicitly register, e.g.  PageAdmin
to your swapped Page model.

No docs update yet, let's first agree on code and naming conventions, what will
become the public API for users. This includes whether we provide
default values in the template project settings or in the code.

I also guess this deprecates the field injection feature, before removing it in
a future major version.

Besides that, it must be 100% compatible, no test changes apart from imports.
  • Loading branch information
Hervé Cauwelier committed Jul 27, 2019
1 parent 71304cf commit c1a989d
Show file tree
Hide file tree
Showing 49 changed files with 392 additions and 113 deletions.
9 changes: 9 additions & 0 deletions mezzanine/blog/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,12 @@
from __future__ import unicode_literals

from mezzanine import __version__ # noqa
from mezzanine.utils.models import get_swappable_model


def get_post_model():
return get_swappable_model("BLOG_POST_MODEL")


def get_category_model():
return get_swappable_model("BLOG_CATEGORY_MODEL")
10 changes: 7 additions & 3 deletions mezzanine/blog/feeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@
from django.utils.feedgenerator import Atom1Feed
from django.utils.html import strip_tags

from mezzanine.blog.models import BlogPost, BlogCategory
from mezzanine.blog import get_post_model, get_category_model
from mezzanine.conf import settings
from mezzanine.core.templatetags.mezzanine_tags import richtext_filters
from mezzanine.core.request import current_request
from mezzanine.generic.models import Keyword
from mezzanine.utils.apps import pages_installed
from mezzanine.utils.html import absolute_urls
from mezzanine.utils.sites import current_site_id


User = get_user_model()
BlogPost = get_post_model()
BlogCategory = get_category_model()

try:
unicode
Expand All @@ -43,8 +46,9 @@ def __init__(self, *args, **kwargs):
super(PostsRSS, self).__init__(*args, **kwargs)
self._public = True
page = None
if "mezzanine.pages" in settings.INSTALLED_APPS:
from mezzanine.pages.models import Page
if pages_installed():
from mezzanine.pages import get_page_model
Page = get_page_model()
try:
page = Page.objects.published().get(slug=settings.BLOG_SLUG)
except Page.DoesNotExist:
Expand Down
4 changes: 3 additions & 1 deletion mezzanine/blog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

from django import forms

from mezzanine.blog.models import BlogPost
from mezzanine.blog import get_post_model
from mezzanine.core.models import CONTENT_STATUS_DRAFT


BlogPost = get_post_model()

# These fields need to be in the form, hidden, with default values,
# since it posts to the blog post admin, which includes these fields
# and will use empty values instead of the model defaults, without
Expand Down
7 changes: 5 additions & 2 deletions mezzanine/blog/management/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@
from django.utils.encoding import force_text
from django.utils.html import strip_tags

from mezzanine.blog.models import BlogPost, BlogCategory
from mezzanine.blog import get_post_model, get_category_model
from mezzanine.conf import settings
from mezzanine.core.models import CONTENT_STATUS_DRAFT
from mezzanine.core.models import CONTENT_STATUS_PUBLISHED
from mezzanine.generic.models import Keyword, ThreadedComment
from mezzanine.pages.models import RichTextPage
from mezzanine.pages import get_rich_text_page_model
from mezzanine.utils.html import decode_entities

User = get_user_model()
BlogPost = get_post_model()
BlogCategory = get_category_model()
RichTextPage = get_rich_text_page_model()


class BaseImporterCommand(BaseCommand):
Expand Down
2 changes: 2 additions & 0 deletions mezzanine/blog/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Migration(migrations.Migration):
('site', models.ForeignKey(editable=False, to='sites.Site', on_delete=models.CASCADE)),
],
options={
'swappable': 'BLOG_CATEGORY_MODEL',
'ordering': ('title',),
'verbose_name': 'Blog Category',
'verbose_name_plural': 'Blog Categories',
Expand Down Expand Up @@ -60,6 +61,7 @@ class Migration(migrations.Migration):
('user', models.ForeignKey(related_name='blogposts', verbose_name='Author', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
],
options={
'swappable': 'BLOG_POST_MODEL',
'ordering': ('-publish_date',),
'verbose_name': 'Blog post',
'verbose_name_plural': 'Blog posts',
Expand Down
20 changes: 17 additions & 3 deletions mezzanine/blog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
from mezzanine.utils.models import AdminThumbMixin, upload_to


class BlogPost(Displayable, Ownable, RichText, AdminThumbMixin):
class AbstractBlogPost(Displayable, Ownable, RichText, AdminThumbMixin):
"""
A blog post.
"""

categories = models.ManyToManyField("BlogCategory",
categories = models.ManyToManyField(settings.BLOG_CATEGORY_MODEL,
verbose_name=_("Categories"),
blank=True, related_name="blogposts")
allow_comments = models.BooleanField(verbose_name=_("Allow comments"),
Expand All @@ -33,6 +33,7 @@ class BlogPost(Displayable, Ownable, RichText, AdminThumbMixin):
admin_thumb_field = "featured_image"

class Meta:
abstract = True
verbose_name = _("Blog post")
verbose_name_plural = _("Blog posts")
ordering = ("-publish_date",)
Expand Down Expand Up @@ -64,16 +65,29 @@ def get_absolute_url(self):
return reverse(url_name, kwargs=kwargs)


class BlogCategory(Slugged):
class BlogPost(AbstractBlogPost):

class Meta(AbstractBlogPost.Meta):
swappable = 'BLOG_POST_MODEL'


class AbstractBlogCategory(Slugged):
"""
A category for grouping blog posts into a series.
"""

class Meta:
abstract = True
verbose_name = _("Blog Category")
verbose_name_plural = _("Blog Categories")
ordering = ("title",)

@models.permalink
def get_absolute_url(self):
return ("blog_post_list_category", (), {"category": self.slug})


class BlogCategory(AbstractBlogCategory):

class Meta(AbstractBlogCategory.Meta):
swappable = 'BLOG_CATEGORY_MODEL'
4 changes: 3 additions & 1 deletion mezzanine/blog/templatetags/blog_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
from django.db.models import Count, Q
from django.utils import timezone

from mezzanine.blog import get_post_model, get_category_model
from mezzanine.blog.forms import BlogPostForm
from mezzanine.blog.models import BlogPost, BlogCategory
from mezzanine.generic.models import Keyword
from mezzanine import template

User = get_user_model()
BlogPost = get_post_model()
BlogCategory = get_category_model()

register = template.Library()

Expand Down
14 changes: 9 additions & 5 deletions mezzanine/blog/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@
from django.template import Context, Template
from django.test import override_settings

from mezzanine.blog.models import BlogPost
from mezzanine.blog import get_post_model
from mezzanine.conf import settings
from mezzanine.core.models import CONTENT_STATUS_PUBLISHED
from mezzanine.pages.models import Page, RichTextPage
from mezzanine.pages import get_page_model, get_rich_text_page_model
from mezzanine.utils.apps import accounts_installed, pages_installed
from mezzanine.utils.tests import TestCase


BlogPost = get_post_model()
Page = get_page_model()
RichTextPage = get_rich_text_page_model()


class BlogTests(TestCase):

def test_blog_views(self):
Expand All @@ -38,9 +44,7 @@ def test_blog_views(self):
response = self.client.get(blog_post.get_absolute_url())
self.assertEqual(response.status_code, 200)

@skipUnless("mezzanine.accounts" in settings.INSTALLED_APPS and
"mezzanine.pages" in settings.INSTALLED_APPS,
"accounts and pages apps required")
@skipUnless(accounts_installed() and pages_installed(), "accounts and pages apps required")
def test_login_protected_blog(self):
"""
Test the blog is login protected if its page has login_required
Expand Down
7 changes: 6 additions & 1 deletion mezzanine/blog/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
from mezzanine.core.translation import (TranslatedSlugged,
TranslatedDisplayable,
TranslatedRichText)
from mezzanine.blog.models import BlogCategory, BlogPost
from mezzanine.blog import get_post_model, get_category_model


BlogPost = get_post_model()
BlogCategory = get_category_model()


class TranslatedBlogPost(TranslatedDisplayable, TranslatedRichText):
Expand All @@ -12,5 +16,6 @@ class TranslatedBlogPost(TranslatedDisplayable, TranslatedRichText):
class TranslatedBlogCategory(TranslatedSlugged):
fields = ()


translator.register(BlogCategory, TranslatedBlogCategory)
translator.register(BlogPost, TranslatedBlogPost)
4 changes: 3 additions & 1 deletion mezzanine/blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
from django.template.response import TemplateResponse
from django.utils.translation import ugettext_lazy as _

from mezzanine.blog.models import BlogPost, BlogCategory
from mezzanine.blog.models get_post_model, get_category_model
from mezzanine.blog.feeds import PostsRSS, PostsAtom
from mezzanine.conf import settings
from mezzanine.generic.models import Keyword
from mezzanine.utils.views import paginate

User = get_user_model()
BlogPost = get_post_model()
BlogCategory = get_category_model()


def blog_post_list(request, tag=None, year=None, month=None, username=None,
Expand Down
3 changes: 2 additions & 1 deletion mezzanine/boot/lazy_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
NotRegistered, AlreadyRegistered)
from django.shortcuts import redirect

from mezzanine.utils.apps import pages_installed
from mezzanine.utils.importing import import_dotted_path


Expand Down Expand Up @@ -104,7 +105,7 @@ def urls(self):
url("^displayable_links.js$", displayable_links_js,
name="displayable_links_js"),
]
if "mezzanine.pages" in settings.INSTALLED_APPS:
if pages_installed():
from mezzanine.pages.views import admin_page_ordering
urls.append(url("^admin_page_ordering/$", admin_page_ordering,
name="admin_page_ordering"))
Expand Down
1 change: 1 addition & 0 deletions mezzanine/conf/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
class TranslatedSetting(TranslationOptions):
fields = ('value',)


translator.register(Setting, TranslatedSetting)
3 changes: 2 additions & 1 deletion mezzanine/core/management/commands/createdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ def create_pages(self):
if self.verbosity >= 1:
print("\nCreating demo pages: About us, Contact form, "
"Gallery ...\n")
from mezzanine.galleries.models import Gallery
from mezzanine.galleries import get_gallery_model
Gallery = get_gallery_model()
call_command("loaddata", "mezzanine_optional.json")
zip_name = "gallery.zip"
copy_test_to_media("mezzanine.core", zip_name)
Expand Down
9 changes: 3 additions & 6 deletions mezzanine/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@
from mezzanine.core.managers import DisplayableManager, CurrentSiteManager
from mezzanine.generic.fields import KeywordsField
from mezzanine.utils.html import TagCloser
from mezzanine.utils.models import base_concrete_model, get_user_model_name
from mezzanine.utils.models import base_concrete_model
from mezzanine.utils.sites import current_site_id, current_request
from mezzanine.utils.urls import admin_url, slugify, unique_slug


user_model_name = get_user_model_name()


def wrapped_manager(klass):
if settings.USE_MODELTRANSLATION:
from modeltranslation.manager import MultilingualManager
Expand Down Expand Up @@ -507,7 +504,7 @@ class Ownable(models.Model):
Abstract model that provides ownership of an object for a user.
"""

user = models.ForeignKey(user_model_name, on_delete=models.CASCADE,
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
verbose_name=_("Author"), related_name="%(class)ss")

class Meta:
Expand Down Expand Up @@ -577,7 +574,7 @@ class SitePermission(models.Model):
access.
"""

user = models.OneToOneField(user_model_name, on_delete=models.CASCADE,
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,
verbose_name=_("Author"), related_name="%(class)ss")
sites = models.ManyToManyField("sites.Site", blank=True,
verbose_name=_("Sites"))
Expand Down
8 changes: 4 additions & 4 deletions mezzanine/core/sitemaps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from django.contrib.sitemaps import Sitemap
from django.contrib.sites.models import Site

from mezzanine.conf import settings
from mezzanine.core.models import Displayable
from mezzanine.utils.apps import blog_installed
from mezzanine.utils.sites import current_site_id


blog_installed = "mezzanine.blog" in settings.INSTALLED_APPS
if blog_installed:
from mezzanine.blog.models import BlogPost
if blog_installed():
from mezzanine.blog import get_post_model
BlogPost = get_post_model()


class DisplayableSitemap(Sitemap):
Expand Down
Loading

0 comments on commit c1a989d

Please sign in to comment.