I'm trying to setup a Wagtail site with an article to pages structure but I'm struggling. A review article for example may have an introduction page, a benchmark page and a conclusion page. I want to work out how to allow this relationship in wagtail and have it so that editors can add multiple pages to the same article on the same page. I can imagine the pages interface looking a bit like how you have content, promote and settings on pages but with the ability to add, rename and reorder pages. I've tried using a foreign key on a page model that links to an article but I can't get it to be shown in the admin the way I want.
Here is the django version of model layout I was looking to use. You have a parent article that is then made up of one or multiple pages. The pages should be editable, orderable and be created from within one panel in the admin with streamfields:
Class Article(models.Model)
STATE_DRAFT = 0
STATE_REVIEW= 1
STATE_PUBLICATION = 2
STATE_HIDDEN = 3
STATE = (
(STATE_DRAFT, 'draft'),
(STATE_REVIEW, 'pending review'),
(STATE_PUBLICATION, 'ready for publication'),
(STATE_HIDDEN, 'hide and ignore'),
)
title = models.CharField(_('title'), max_length=256)
slug = models.SlugField(
_('slug'), unique=True, blank=True, default='', max_length=256
)
description = models.TextField(
_('description'), max_length=256, blank=True, default=''
)
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='article'
)
publication = models.DateTimeField(
null=True, blank=True, default=None, db_index=True, help_text='''
What date and time should the article get published
'''
)
state = models.PositiveIntegerField(
default=0, choices=STATE, help_text='What stage is the article at?'
)
featured = models.BooleanField(
default=False,
help_text='Whether or not the article should get featured'
)
class Page(Page):
article = models.ForeignKey(
'Article', on_delete=models.CASCADE, related_name='pages'
)
title = models.CharField(max_length=256)
number = models.PositiveIntegerField(default=1) # So pages are ordered
body = models.TextField(blank=True)
As per my comment I don't think you'll be able to achieve everything you're looking for short of implementing an entirely bespoke CMS - but if you're able to bend the UI and data modelling requirements, then Wagtail's RoutablePageMixin is one possible way of achieving the general pattern of editing an article as a single unit, while presenting it as multiple pages on the front-end.
In this approach, you'd make Article a Wagtail Page model, with all of the sub-page content defined as fields (or InlinePanel child models) on that model. (If you want to split the content entry into tabs within the editing interface, see Customising the tabbed interface, although this won't support dynamically adding / reordering them.) You'd then define a URL route and template for each subpage of the article:
from wagtail.core.models import Page
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class ArticlePage(RoutablePageMixin, Page):
intro = StreamField(...)
main_page = StreamField(...)
conclusion = StreamField(...)
#route(r'^$')
def intro_view(self, request):
render(request, 'article/intro.html', {
'page': self,
})
#route(r'^main/$')
def main_page_view(self, request):
render(request, 'article/main_page.html', {
'page': self,
})
#route(r'^conclusion/$')
def conclusion_view(self, request):
render(request, 'article/conclusion.html', {
'page': self,
})
In this example the three sub-pages are hard-coded, but with some more work (perhaps an InlinePanel child model with a slug field and a StreamField) you could make the subpages dynamic.
I saw gasman already provided an answer to you question, but I'm still going to write up an answer for two reasons:
I think you need some more pointers as to why gasmans' proposal is a better solution than yours, but it's way to much to write in a comment.
I have implemented a similar solution before, where there is a top level 'Article'-like object with multiple reorderable child objects, where the actual content resides.
Why you should make Article a Page subclass
You chose not to make Article a subclass of Page, and you said it was because the Article itself does not contain any content, only metadata about an article. That is not a very strange thought process, but I think you're looking at the wrong requirements for your Article model.
Let's look at Wagtail's own Page model. What kind of functionality does it provide out of the box?
It provides a tree structure with parent and child pages, so that your page can be placed somewhere in the hierarchy of your website
It provides a slug_field, so that Wagtail can automatically handle linking to your page.
It provides functionality for drafting, publishing and unpublishing.
Wagtail doesn't dictate anything about content, leaving you to decide what kind of content you want to put on your Page subclass, if any. Examples of Pages that do not have a body would be:
Contact forms.
Blog index pages.
Good questions you could ask when deciding whether you want a Model to be a subclass of a Page are:
Do I want this object to have it's own url?
Do I want to be able to place this object somewhere inside my website hierarchy?
Do I want to have SEO advantages for the object?
Do I want to be able to publish/unpublish this object or not?
In your case of the Article, you could say yes to almost al these question, so it'd be wise to make it a Page subclass. That way, you don't have to reinvent the wheel.
How you define the actual 'body' of your page is up to you.
You can place the actual content in either snippets, or subpages to that Article. Or you can even choose to create a list of StreamFields inside your model.
How to implement ordered subcontent.
I have implemented a structure like this before.
The way I did this was very similar to what gasman proposes.
In my case, I needed to create a website where you could find an object (like you article) and display different types of explanation modules for it. For each document, I created a ArticlePage, and for each explanation module, I created a snippet called ExplanationModule.
I then created a through model with an ordering, and added a RoutablePageMixin to the class like gasman explains.
The structure looks something like this:
#register_snippet
class ArticlePageModule(models.Model):
...
title = models.CharField(max_length=100)
body = StreamField(LAYOUT_STREAMBLOCKS, null=True, blank=True)
panels = [
FieldPanel('title'),
StreamFieldPanel('body'),
]
class ArticlePageModulePlacement(Orderable, models.Model):
page = ParentalKey('articles.ArticlePage', on_delete=models.CASCADE, related_name='article_module_placements')
article_module = models.ForeignKey(ArticlePageModule, on_delete=models.CASCADE, related_name='+')
slug = models.SlugField()
panels = [
FieldPanel('slug'),
SnippetChooserPanel('article_module'),
]
class ArticlePage(Page, RoutablePageMixin):
# Metadata and other member values
....
content_panels = [
...
InlinePanel('article_module_placements', label="Modules"),
]
#route(r'^module/(?P<slug>[\w\-]+)/$')
def page_with_module(self, request, slug=None):
self.article_module_slug = slug
return self.serve(request)
def get_context(self, request):
context = super().get_context(request)
if hasattr(self, 'article_module_slug'):
context['ArticlePageModule'] = self.article_module_placements.filter(slug = self.article_module).first().article_module
return context
What this does is the following:
Create a ArticlePageModule snippet, which is just some kind of content, like a title and a body.
Create a ArticlePageModulePlacement which links a ArticlePage to a module, and adds the following:
A slug
An Ordering (Because it subclasses the Orderable mixing)
Create a ArticlePage which does two things:
Define a ArticlePageModuleplacement panel, which allows you to add ArticlePageModulePlacements
Subclass RoutablePagemixin, as described in gasman's answer.
This provides you with a Wagtail-proof, reusable and robust way of creating Articles with SubContent.
The modules don't show up in tabs, but will be shown on the page's layout page under a panel called 'Modules'.
I've recently complete the Django Rest Framework api tutorial and am having a difficult time understanding specifically how it's used as a backend for an application I plan to develop (this is my first venture into backend development). To put more simply, I don't understand how querying will work from the front end. Navigating through the api with either the browser or httpie makes sense, but I'm at a loss for how a frontend extracts specified data from a model.
For example, let's say I have the following:
Models
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
highlighted = models.TextField()
Serializers
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style', 'url', 'highlight')
Views
class SnippetViewSet(viewsets.ModelViewSet):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
If I'm a user on the other end of an application, how would I query 'language' inside of the Snippet model? How would I have access to whatever information is in 'language', and in what way would a frontend need to interact with my api to obtain this information?
My problem isn't necessarily how to build the api, but how to interact with it. Any help is greatly appreciated.
(Django 2.0, Python 3.5)
Ok, huge thanks to the Udemy course posted by Mark Winterbottom for the Django Rest Framework. I'll go ahead and leave this here for anybody else struggling with understanding some basic ideas in the Django Rest Framework.
JSON data is extracted by having the frontend hit urls determined by your api. So this becomes a question of "how do I implement some search functionality that's in a url?".
Django uses the Model, View, Controller pattern. A Model is what interacts with the database, and allows you to extract data from it without needing to understand how to query using actual SQL code (uses something called Object Relational Mapping to do this, or ORM, and your Models are in the model.py file). The Controller, how you interact with the pulled data to create/read/update/delete stuff in your api is saved in views.py (a bit counter intuitive, seeing as the View is what you would have in your templates folder [HTML pages and such]).
You can implement something called filters in your Controller (views.py) to allow you to search by specific information to get that ?search=whateveryouresearching url, by including the following:
from rest_framework import filters
and adding this to your ViewSet you want to search:
filter_backends = (filters.SearchFilter,) #allows for search functionality
search_fields = ('name','email') #which can be any Field in your viewset
This ?search=whateveryouresearching created by the filter is how some front end device will access specific searched information (such as a specific user input linke English or Mandarin inside 'language').
I have trouble to display the ''saved''/''liked'' posts of my users in django/admin. I would like to have a field in the Adminpage to show which user likes which posts. I made an Userprofile model where all extra information (besides the one on the given django admin user Profile) are stored. so here is my model View:
class UserProfile(models.Model):
user = models.OneToOneField(User, null=True)
#likes = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True,default=1, related_name='likes')
likedPosts=models.ManyToManyField('self')
Field1 = models.CharField(max_length=50,default='Sunny')
Field2 = models.CharField(max_length=50,default='')
class Meta:
ordering =['-user']
#def __unicode__(self):
# return self.user.username
User.profile =property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
right now in the liked post field I have only some usernames or "User object"
I tried all kinds of combinations to get the information into the admin page but as you can see I did not make it.
I tried to change the unicode and of course the liked post line. If you need more information please tell me so. I appreciate every kind of help.
django admin isn't really meant to support many to many relationships from both directions in the django admin. However, the link below contains a workaround that I think should address your problem with a better explanation of why many-to-many relationships are only shown from one side by default.
(many-to-many in list display django).
so for everybody who wants to do something similar this worked for me:
class UserProfile(models.Model):
likedPosts = models.ManyToManyField('self',default=None,blank=True)
def __unicode__(self):
return "{0}".format(self.user.likes.all())
I am trying to create a function that allows individuals to post something on an associated userpage. I have created both the model ‘newpost’ and ‘newpostform’ (below). I am having trouble writing the view function to look at the current URL of the page and then take that parameter and attach it to the newpost model’s ForeignKey field automatically. For example, if I am at the URL myapp.com/userpage1 and I click on the “post” button on that page, I want to create a newpost object which automatically has the ForeignKey field filled in as ‘userpage1’. Basically, I am trying to create an app where people can easily navigate userpages by entering the userpage parameter into the URL and easily make posts on those pages quickly and concisely - kind of like how reddit's subreddit system works by entering the name of the subreddit into the URL bar. Thanks for any help and hints.
model:
class newpost(models.Model):
newlinktag = models.ForeignKey(‘userpage’) #tags link to which userpage the post belongs to
postcontent = models.CharField(max_length=1024)
postdate = models.DateTimeField(auto_now_add=True) #submission timestamp.
postlikes = models.IntegerField(null=False, default=0)
def __unicode__(self):
return self.postcontent
form:
class newpostform(forms.ModelForm):
postcontentform = models.CharField(max_length=1024)
class Meta:
model = newpost
urls.py:
url(r'^(?P<url_user_id>[\w\-]+)/$', your_view)
views.py
def your_view(request, url_user_id)
# you have the foreign key in the url_user_id field.
...
if request.POST:
new_post_with_foreign_key = newpost(newlinktag=url_user_id, ...)
...
new_post_with_foreign_key.save()
Don't do this in the form. Exclude the FK field from the modelform altogether, and set it in the view on save.
if form.is_valid():
post = form.save(commit=False)
post.newlinktag = page
post.save()
(You might like to consider following PEP8 and using some CapitalLetters in your class names, and underscore_names in your field names. It'll make your code much easier to read.)
I have this modelViewSet
class LikeViewSet(viewsets.ModelViewSet):
queryset = Likes.objects.all()
serializer_class = LikeSerializer
filter_fields = ('user','post')
def delete(self, request, pk, format=None):
post = Likes.objects.get(pk=pk)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
I'm trying to filter using the url such as:
http://localhost:8000/likes/?user=anon&post=1
And then delete that specific result that I get from django but django keeps on giving me
delete() takes at least 3 arguments (2 given)
I can't really figure out why. Can anyone help please? Thanks! I'm using Django Rest Framework
EDIT: This is the model for the LikeViewSet:
class Likes(models.Model):
user = models.ForeignKey(Profile, related_name='liker')
post = models.ForeignKey(Post, related_name=' post' )
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
The idea is, it's a model table for a relationship between a user model and a post model so the filtering has to be done in the url that way
When you're using a ViewSet, you should use the destroy() method rather than delete().
See documentation here:
A ViewSet class is simply a type of class-based View, that does not
provide any method handlers such as .get() or .post(), and instead
provides actions such as .list() and .create().
Based on your code, it doesn't look like you're doing anything unique in the destroy/delete method. Are you fine with just using the default destroy function?