I've registred a snippet which has a foreign key to a somewhat huge table in wagtail 3. When attempting to click on an entry to see the detail view which displays the fieldpanels, I have about 5-10 seconds of loading which tells me massive amounts of queries are being made. Is there any way to use select_related() on a fieldpanel field?
In this particular case I'd like to use select_related on "day"
#register_snippet
class EventSnippet(index.Indexed, Event):
panels = [
FieldPanel('name'),
FieldPanel('day'),
]
search_fields = [
index.SearchField('name', partial_match=True),
index.RelatedFields('day', [index.SearchField('calendar_year')]),
]
class Meta:
proxy = True
Related
I have a model where users can upvote other users for specific topics. Something like:
#models.py
Class Topic(models.Model):
name = models.StringField()
def __str__(self):
return str(self.name)
Class UserUpvotes(models.Model):
"""Holds total upvotes by user and topic"""
user = models.ForeignKey(User)
topic= models.ForeignKey(Topic)
upvotes = models.PositiveIntegerField(default=0)
Using DRF, I have an API that returns the following: topic_id, topic_name, and upvotes, which is the total upvotes for a given topic.
One of the project requirements is for the API to use these field names specifically: topic_id, topic_name, and upvotes
#serializers.py
class TopicUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.StringRelatedField(source="topic")
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
My trouble is aggregating these fields. I'm filtering the UserUpvotes by user or team and then aggregating by topic.
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_id": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_id": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
At first, I tried creating a TopicSerializer, and then assigning it to the topic field in TopicUpvotesSerializer. But then, the resulting json would have a nested "topic" field and the aggragation would fail.
Attempt 1
#views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
My problem is that the topic_id and topic_name fields are not showing. I get something like:
[
{
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": "12",
"upvotes": 3
},
]
Attempt 2
Another queryset attempt:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
Which yields:
[
{
"upvotes": 14
},
{
"upvotes": 3
},
]
The aggregation worked on the queryset level, but the serializer failed to find the correct fields.
Attempt 3
This was the closest I got:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.values("topic_id", "topic", "upvotes")
.order_by("-upvotes")[:n]
)
[
{
"topic_name": 3,
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "12",
"upvotes": 3
},
]
I have no idea why "topic_name" is simply transforming the "topic_id" into a string, instead of calling the string method.
Work with a serializer for the topic:
class TopicSerializer(serializers.ModelSerializer):
upvotes = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
fields = ['id', 'name', 'upvotes']
then in the ModelViewSet, you annotate:
from django.db.models import Sum
from rest_framework.viewsets import ModelViewSet
class TopicViewSet(ModelViewSet):
serializer_class = TopicSerializer
queryset = Topic.objects.annotate(upvotes=Sum('userupvotes__upvotes'))
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_name": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
The serialized FK will always give you the ID of the related model. I am not sure why you name it topic_name if that is equal to an ID. Now, if you really want to get the name field of the Topic model
in the topic_name = serializers.StringRelatedField(source="topic") you should give it a source="topic.name"
However, if you trying to get the ID of the relation you can still use ModelSerializer :
class TopicUpvotesSerializer(serializers.ModelSerializer):
class Meta:
model = UserUpvotes
fields = "__all__"
#willem-van-onsem's answer is the correct one for the problem as I had put it.
But... I had another use case (sorry! ◑﹏◐), for when the Users API used UserUpvotes serializer as a nested field. So I had to find another solution. This is was I eventually ended up with. I'm posting in case it helps anyone.
class UserUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.SerializerMethodField()
def get_topic_name (self, obj):
try:
_topic_name = obj.topic.name
except TypeError:
_topic_name = obj.get("skill__name", None)
return _topic_name
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
I still have no idea why the SerializerMethodField works and the StringRelatedField field doesn't. It feels like a bug?
Anyways, the rub here is that, after the values().annotate() aggregation, obj is no longer a QuerySet, but a dict. So accessing namedirectly will give you a 'UserUpvotes' object is not subscriptable error.
I don’t know if there are any other edge cases I should be aware of (this is when I REALLY miss type hints in Django), but it works so far
I have a snippet which is a proxy of one of my standard django models.
search_fields works fine when filtering on standard fields, the problem is I can't seem to get foreign keys to work.
This page has an example on the bottom that shows how to create searchable snippets:
https://docs.wagtail.org/en/stable/topics/snippets.html
The main model has a field called "day" which is a foreign key to a Day-table. A day has a calendar_year, which I would like to be able to filter on while searching in the wagtail snippets area. in the def str method I'm able to display the name in the list, the search is the problem here.
Suggestions?
#register_snippet
class EventSnippet(index.Indexed, Event):
# We make a proxy model just to be able to add to this file or potentially if we want custom methods on it.
panels = [
FieldPanel('name'),
]
search_fields = [
index.SearchField('day__calendar_year', partial_match=True), # This prompts an error
index.SearchField('name', partial_match=True),
]
class Meta:
proxy = True
def __str__(self):
return f"{self.name} {self.day.calendar_year}"
When running python manage.py update_index i get the following warning:
EventSnippet.search_fields contains non-existent field 'day__calendar_year
You can't use complex lookups with double-underscores inside SearchField - search queries work by populating a central table (the search index) in advance with the data you're going to be searching on, which means you can't do arbitrary lookups and transformations on it like you would with a standard database query.
However, you can use any method or attribute in SearchField - not just database fields - so you could add a method that returns the year, and use that:
#register_snippet
class EventSnippet(index.Indexed, Event):
# ...
def get_year(self):
return self.day.calendar_year
search_fields = [
index.SearchField('get_year', partial_match=True),
index.SearchField('name', partial_match=True),
]
I am trying to generate a Django model that can handle multiple values in a single field. As such, when the first field is queried through a view, a user should select a value for the second field through a select box.
To give a background of the problem, my seeding fixture looks like this...
[
{
"model":"myapp.location",
"pk":1,
"fields":{
"county": "countyname",
"places":{
"name": "placename",
"name": "placename",
"name": "placename",
"name": "placename",
"name": "placename"
}
}
}
]
In the above scenario, location is the intended name of my model. Now, through a form, I want a user to be presented with 'countynames'. Upon selecting a countyname, the user should be presented with 'placenames' in the selected county for them to choose.
I have tried the following format for the model...
class Location(models.Model):
county = models.CharField(max_length=100)
places = models.CharField(max_length=100, choices=places.name)
def __str__(self):
return self.countyname
Now, I know that the error that is thrown, ('places' is not defined), is warranted. I was asking whether there is a way to define it (places), as it is in the fixture, or if anyone has a better implementation for such a model... any alternative way is welcome and appreciated as I can't think of anything at this point.
So, after fiddling with two models and foreign keys as suggested in the comments above, I decided to amend the model, which also led to changing the fixture. I read about ArrayFields in Postgres + Django here. I amended the field 'places' to be an ArrayField as shown:
from django.contrib.postgres.fields import ArrayField
class Location(models.Model):
county = models.CharField(max_length=100)
places = ArrayField(models.CharField(max_length=100), blank=True)
def __str__(self):
return self.county
Next, it was just a matter of changing the JSON fixture to:
[
{
"model":"myapp.location",
"pk":1,
"fields":{
"county": "countyname",
"places":["placename","placename","placename","placename"]
}
}
]
After running python manage.py loaddata fixturename.json ,
it worked and the DB was seeded!
I have a TimeField in my models.py that uses choices of the form
MORNING = datetime.time(4, 0)
NIGHT = datetime.time(20, 0)
TIME_CHOICES = (
(MORNING, _('Morning')),
(NIGHT, _('Night'))
)
time = models.TimeField(_('Time'), choices=TIME_CHOICES)
serializers.py
class VehiclePlanningSerializer(serializers.ModelSerializer):
class Meta:
model = mymodel
When i create objects via django admin and set the choice to Morning it stores a proper time "04:00:00" in the db. When i use raw data in the API GUI and insert "04:00:00" for time it works also. But when i use the Form on the API GUI and select Morning I get the following error:
"time": [
"\"4 a.m.\" is not a valid choice."
]
So somehow the serializer transforms the Morning choice wrongly when he gets a time(4,0). Is there a way to suppress this transformation?
I'm trying to understand how to get links to all pages in my site that have the currently viewed page listed as a related page via the modelcluster ParentalKey.
The basic setup is as follows:
# RelatedLink inherits from LinkFields,
# both directly copied from the wagtaildemo project
class ParentPageRelatedLink(Orderable, RelatedLink):
page = ParentalKey('ParentPage', related_name='related_links')
class ParentPage(Page):
parent_page_types = ['ParentPage']
subpage_types = ['ParentPage', 'ChildPage']
def child_pages(self):
children = ChildPage.objects.live().descendant_of(self)
return children
ParentPage.content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('body', classname="full"),
InlinePanel(ParentPage, 'related_links', label="Related links"),
]
class ChildPage(Page):
parent_page_types = ['ParentPage']
parent_page_types = ['ChildPage']
def parent_index(self):
return self.get_ancestors().type(ParentPage).last()
ChildPage.content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('body', classname="full"),
InlinePanel(ChildPage, 'related_links', label="Related links"),
]
If understand things correctly, to get each ParentPage that has the current ChildPage in its related_links, I'd have to go through every page listed in ChildPage.parent_page_types, test if the current ChildPage is in the ParentPage.related_links, and then output whatever I need from each of those ParentPages.
Seems like it would be a lot of queries to the db if there are many instances of the page types listed in parent_page_types.
Is there a better way?
For example, does modelcluster enable any sort of backreferencing (like what Flask-SQLAlchemy provides when using db.relashionship(backref="something")) through the ParentalKey that is created in ParentPageRelatedLink? It doesn't look like it from inspecting the database tables.
Edit
Ok so it seems like the related_name from the LinkFields might be a way to do this, but since I can't set it to something like "related_from" since LinkFields is inherited by many different ParentPage-like classes, it seems I have to have individual LinkField classes with their own unique ForeignKey(related_name="something") definitions for each ParentPage... Or do as instructed in the django docs.
But then I might be better of with my initial thought of a loop?
class LinkFields(models.Model):
link_external = models.URLField("External link", blank=True)
link_page = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
related_name='+'
)
link_document = models.ForeignKey(
'wagtaildocs.Document',
null=True,
blank=True,
related_name='+'
)
#property
def link(self):
if self.link_page:
return self.link_page.url
elif self.link_document:
return self.link_document.url
else:
return self.link_external
panels = [
FieldPanel('link_external'),
PageChooserPanel('link_page'),
DocumentChooserPanel('link_document'),
]
class Meta:
abstract = True
Try this:
parent_pages = ParentPage.objects.filter(related_links__linked_page=child_page)
"related_links" is taken from the related_name attribute of the ParentalKey on the model that you want to query. "linked_page" is a field on the ParentPageRelatedLink model that you want to filter on.
https://docs.djangoproject.com/en/1.8/topics/db/queries/#lookups-that-span-relationships
parent_page_types is unrelated to querying pages. It's used to add a constraint to what page types a particular page type can be created under (so you can say a blog entry can only ever be created in a blog for example). It's unrelated to how pages are queried
You should be able to define a #property within the ParentPage class. Something like:
class ParentPage(Page):
...
#property
def related_pages(self):
pages = ParentPageRelatedLink.objects.filter(page_id=self.id)
return pages