I was working on developing a Mezzanine site where a desired feature was to allow marking BlogPosts as login required, the same way that Pages can be marked as login required. After implenting a solution I decided it would be nice to document it for myself, and anyone else who is interested.
The code
A prerequisite to the following code is creating a django app.
Throughout the post the name blog_login_required
is used, but any Django app will do.
Let's get started:
python manage.py startapp blog_login_required
After creating the app add it to your INSTALLED_APPS
setting.
1. settings.py
First let's add a login_required field to BlogPosts using the EXTRA_MODEL_FIELDS Mezzanine setting.
EXTRA_MODEL_FIELDS = ( ( "mezzanine.blog.models.BlogPost.login_required", "BooleanField", ("Login required",), {"default": False}, ), )
If you are using South migrations, which you should be, we need to create the migrations for this. We will put the migrations in an external app to avoid modifying Mezzanine directly. To do this run the following two commands:
$ python manage.py schemamigration blog —auto —stdout » blog_login_required/migrations/0001_add_login_required_to_blogpost.py $ python manage.py migrate blog_login_required
2. views.py
The built in Mezzanine view for BlogPosts doesn't handle login required so I created my own. In this case I opted to recreate the view because the original is very small (4 or 5 lines) and this was more efficient. If it was more complicated I may have opted to use a middleware, the disadvantage of this would be that the middleware would have to look up the blog post just to check login_required and the view would later lookup the same blog post.
from django.contrib.auth import REDIRECT_FIELD_NAME from django.shortcuts import get_object_or_404, redirect from django.utils.http import urlquote from mezzanine.blog.models import BlogPost from mezzanine.conf import settings from mezzanine.utils.views import render def blog_post_detail(request, slug, year=None, month=None, day=None, template="blog/blog_post_detail.html"): """. Custom templates are checked for using the name ``blog/blog_post_detail_XXX.html`` where ``XXX`` is the blog posts's slug. """ blog_posts = BlogPost.objects.published( for_user=request.user).select_related() blog_post = get_object_or_404(blog_posts, slug=slug) if blog_post.login_required and not request.user.is_authenticated(): path = urlquote(request.get_full_path()) bits = (settings.LOGIN_URL, REDIRECT_FIELD_NAME, path) return redirect("%s?%s=%s" % bits) context = {"blog_post": blog_post, "editable_obj": blog_post} templates = [u"blog/blog_post_detail_%s.html" % unicode(slug), template] return render(request, templates, context)
3. urls.py
Next we need to create url patterns to instruct the site to use the new blog post detail rather than Mezzanine's. Because of the required order of the blog url patterns I recreated all of them, there may be a better solution, but this is the best I could think of. These patterns can be placed directly in your project's urls, they just need to show up before Mezzanine urls are included.
url("^%s%sfeeds/(?P<format>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_feed", name="blog_post_feed"), url("^%s%stag/(?P<tag>.*)/feeds/(?P<format>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_feed", name="blog_post_feed_tag"), url("^%s%stag/(?P<tag>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_list", name="blog_post_list_tag"), url("^%s%scategory/(?P<category>.*)/feeds/(?P<format>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_feed", name="blog_post_feed_category"), url("^%s%scategory/(?P<category>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_list", name="blog_post_list_category"), url("^%s%sauthor/(?P<username>.*)/feeds/(?P<format>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_feed", name="blog_post_feed_author"), url("^%s%sauthor/(?P<username>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_list", name="blog_post_list_author"), url("^%s%sarchive/(?P<year>\d{4})/(?P<month>\d{1,2})%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_list", name="blog_post_list_month"), url("^%s%sarchive/(?P<year>.*)%s$" % _blog_format_string, "mezzanine.blog.views.blog_post_list", name="blog_post_list_year"), url("^%s%s(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/" "(?P<slug>.*)%s$" % _blog_format_string, "blog_login_required.views.blog_post_detail", name="blog_post_detail_date"), url("^%s%s(?P<slug>.*)%s$" % _blog_format_string, "blog_login_required.views.blog_post_detail", name="blog_post_detail"),
You also need to define the _blog_format_string somewhere above your url patterns, it looks like this:
from mezzanine.conf import settings BLOG_SLUG = settings.BLOG_SLUG.rstrip("/") _blog_format_string = ( BLOG_SLUG, "/" if settings.BLOG_SLUG else "", "/" if settings.APPEND_SLASH else "", )
4. admin.py
Finally, I monkey patched the Mezzanine admin to include login required. To
do this I added the following line to admin.py in blog_login_required
.
For this to work blog_login_required
needs to appear before
mezzanine.blog
in INSTALLED_APPS
.
BlogPostAdmin.fieldsets[0][1]["fields"].extend(["login_required"])
Comments