Non-model based serialization in Wagtail API response - wagtail

I'm retrieving a list of pages via a custom API endpoint called sitemap. The goal of this endpoint is to return just the URL and last_updated flag so that I can generate a sitemap.xml file for my website. I can't apply a custom serializer to the model as I don't want to affect the normal pages API endpoint we're using.
Is it possible to apply a serializer to an API queryset rather than serializing it at the model level?
I could probably do this with a list comprehension, but a custom serializer feels like a better solution.

So it turns out that it's much easier than I thought. I created a custom serializer:
class SitemapSerializer(serializers.Serializer):
page_name = serializers.CharField()
url_slug = serializers.CharField()
Then applied that serializer to my APIEndpoint class:
class SitemapPagesAPIEndpoint(PagesAPIEndpoint):
base_serializer_class = SitemapSerializer
I think the thing that threw me off was that the property wasn't called serializer_class.

Related

Parsing JSON and save values to database in django

Am getting a post request from a ussd gateway. The post has a body in JSON format["phone":"07xxxxxx":"shortCode":"*100#":"text":"1"]
I need to parse this JSON,get individual values and save in django as a record. How do I go about this?
next time please provide some code with what you tried so far. It shows other guys, that you at least tried to figure it out yourself. The problem you have is well documented in the DRF Tutorial and pretty straight forward. As you are new here on SO I will show it to you, but next time please put some more effort in it before writing a question.
You need four things:
-model
-serializer
-url
-view
The model represents the way your database table is built up. Based on your json data it could look like this:
models.py
from django.db import models
class ExampleModel(models.Model):
phone = models.CharField(max_length=10) # There are third party fields like django-phonenumber-field
shortCode = models.CharField(max_length=10)
text = models.CharField(max_length=50)
Then you need a serializer to handle your incoming json data and validate them. Create a serialzers.py file. In this example we use modelserializer, but there are some more like hyperlinkedmodelserializer or the serializer baseclass to create a fully custom serializer. For your purpose the modelserializer is a good way to go.
serializers.py
from rest_framework import serializers
from .models import ExampleModel
class ExampleModelSerializer(serializers.Modelserializer):
class Meta:
model = ExampleModel
fields = '__all__'
Now we need a view. For your use case you need a view that handles the post request. I will use the generics.CreateAPIView. There is a lot that goes under the hood. You should do the tutorial to understand how you get from the APIView to the generic Views. Keep in mind that the CreateAPIView only handles the creation of new items. There are more generic views to handle list, retrieve, update and so on.
views.py
from rest_framework import generics
from .serializers import ExampleModelSerializer
class MyCreateView(generics.CreateAPIView):
serializer_class = ExampleModelSerializer
The last thing you have to do is to define a url path.
urls.py
from django.urls import path
from yourappname import views
urlpatterns = [
path('example/', views.MyCreateView.as_view()),
]
All of the things I wrote are covered in the tutorial.In the future, if something is not working and you could not solve it, provide a short example and you will find people to help you. But they won't write the entire code for you like I did this time. The reason I did it was that you are new and it was pretty straight forward to write it down. Good luck for you project.

Is it posible to use django_select2 widgets with a Wagtail ParentalManyToManyField

I need a complex selection widget because there are a lot of options in a multiple select widget. But I see HeavySelect2MultipleWidget needs views and urls to use it. I think there is not possible it in Wagtail by default.
This is the code:
class Resource(Page):
authors = ParentalManyToManyField('Authors', blank=True)
content_panels = Page.content_panels + [
FieldPanel('authors', widget=forms.CheckboxSelectMultiple)
]
It would be nice to use
FieldPanel('authors', widget=HeavySelect2MultipleWidget)
but it raises a
You must ether specify "data_view" or "data_url".
According to the Django-Select2 documentation you have to initiate (call) the widget with the attributes first.
As the error says - data_url or data_view has not been provided to the widget.
It will be up to you to generate that view or url specific to your use case. You could override the serve method of your page model to serve the data and provide the appropriate url pattern or simply create a different view altogether as documented here (see Heavy Components section).
For example:
FieldPanel(
'authors',
widget=HeavySelect2MultipleWidget(
data_url='url/to/json/resonse'
)
)
HeavySelect2MultipleWidget extends HeavySelect2Widget - see docs for more detail:
http://django-select2.readthedocs.io/en/latest/django_select2.html#django_select2.forms.HeavySelect2Widget
I have not used this specific widget but have used Django-Select2 in Wagtail in a similar set up and it has worked well.

Single piece of content, multiple URLs?

I have a use case that I could use some advice on.
We publish multiple products, each of which has it's own subtree on the site. Generally, a piece of content gets published to just a single product, e.g. a news article gets published to product A and can be accessed at one URL.
However, sometimes we have content that we want to publish to multiple products, e.g. a single news article gets published to products A, B, and C and will be available at 3 different URLs.
With our current CMS we end up doing this by copying and pasting the content, which is a hassle for editors, especially if the content needs to be updated.
An ideal scenario would be where and editor edits the content in one place, specifies the products to publish to, and the content is served by more than one URL and with a template that is product-specific.
It seems that RoutablePageMixin could be useful here, but I'm not sure how to handle letting the editor specify the destination products and making the routing aware of that choice.
Has anyone solved a similar problem using Wagtail?
I have solved a similar problem in Wagtail, the RoutablePageMixin is the key to solving this problem.
If you have /blog/A/slug-product/, /blog/B/slug-product/, /blog/C/slug-product/ , then you can get the slug value slug-product here, then use this value to search the distinct content in your db.
class BlogPage(RoutablePageMixin, Page):
def get_posts(self):
return PostPage.objects.descendant_of(self).live()
#route(r'^(\d{4})/(\d{2})/(\d{2})/(.+)/$')
def post_by_date_slug(self, request, year, month, day, slug, *args, **kwargs):
post_page = self.get_posts().filter(slug=slug).first()
return Page.serve(post_page, request, *args, **kwargs)
As you can see, I did not use the date info of the url but the slug value to get the blog post object, you can follow the pattern here to use regex to match the url you want.
If the slug values in urls are also different, this solution might not work very well, but in most cases, this solution can work fine.
I have written a blog post talking about how to use RoutablePageMixin to make the page routable, you can check this link if you wang to get more about RoutablePageMixin.
Routable Page
Rather than thinking of your news articles as being child objects of one or more products, it might help to think of them as one big pool of news articles which are categorised by product. Your product page will then effectively be a filtered index page of news articles.
Here's how I'd model it:
If you want your news articles to exist at a canonical URL that's independent of any particular category, or you want to make use of page moderation and/or previewing, then define NewsArticle as a page model; otherwise, define it as a snippet or a ModelAdmin-managed model.
On the NewsArticle model, have an InlinePanel where editors can associate as many related products as required:
class NewsArticle(Page):
body = RichTextField()
date = models.DateField()
content_panels = Page.content_panels + [
FieldPanel('body'),
FieldPanel('date'),
InlinePanel('related_products', label="Related products"),
]
class NewsArticleRelatedProduct(Orderable):
news_article = ParentalKey(NewsArticle, related_name='related_products')
product = models.ForeignKey(ProductPage, on_delete=models.CASCADE, related_name='news_articles')
panels = [
PageChooserPanel('product'),
]
On your ProductPage model, add a method that returns a queryset of news items, filtered and sorted appropriately:
class ProductPage(Page):
# ...
def get_news_articles(self):
return self.news_articles.live().order_by('-date')
You can then loop over the news articles in your product page template, using a tag like {% for news_article in page.get_news_articles %}.

Tastypie ModelResource fields based on methods

So Tastypie automatically generates the fields based on the fields of your Django Model (if you use ModelResource). I would like to use some methods from my Model as fields in the Resource. Is there a good way to do this in Tastypie? I know it's possible to do it using hydrate but that seems a bit hackish.
Try this:
class UserCharField(ModelResource):
stuff = fields.CharField(attribute='get_stuff_method', readonly=True)
class Meta:
queryset = User.objects.all()

Django model defining list of URLFields

I'm pretty new to relational databases and this may be why I'm having this problem but I have a model - Post.
I want it to have variable number of URLs, however Django only seems to have the OneToManyField which requires a model (not a field - which URLField is).
In relational database design, the fields in a table are always scalar values. in your case, such a field would 'a url'. The way you get a collection to apply to a row, you join that row with the rows of another table. In django parlance, that would mean that you need two models, one for the Post objects, and another that links multiple urls with that post.
class Post(models.Model):
pass
class Url(models.Model):
url = models.URLField()
post = models.ForeignKey(Post)
myPost = Post.objects.all().get()
for url in myPost.url_set.all():
doSomething(url.url)
Now you can access urls through a urls member
But if you want to get the admin page for Post to also let you add urls, you need to do some tricks with InlineModelAdmin.
from django.db import models
from django.contrib import admin
class Post(models.Model):
pass
class Url(models.Model):
url = models.URLField()
post = models.ForeignKey(Post)
class UrlAdmin(admin.TabularInline):
model = Url
class PostAdmin(admin.ModelAdmin):
inlines = [UrlAdmin]
admin.site.register(Post, PostAdmin)

Resources