Using reverse (Parental)ManyToManyField in ModelAdmin - django

Similar to my previous question, I'm trying to use the related model within ModelAdmin. (This is because I would like it to be available in both admin views.) This time, however I am using the new ParentalManyToManyField or just a normal ManyToManyField which seem to mess things up.
I wrote the following structure:
class B(Model): # or Orderable
...
edit_handler = TabbedInterface([
ObjectList([
FieldPanel('aes', widget=CheckboxSelectMultiple),
], heading=_('Aes'),
),
])
class A(ClusterableModel):
...
bees = ParentalManyToManyField(
B,
related_name='aes',
blank=True,
)
...
edit_handler = TabbedInterface([
ObjectList([
FieldPanel('bees', widget=CheckboxSelectMultiple),
], heading=_('Bees'),
),
])
When trying to reach the page I receive a Field Error:
Unknown field(s) (aes) specified for B
Is what I'm trying to do not possible yet or did I forget a step?

The ParentalManyToManyField needs to be defined on the parent model (which I assume is meant to be B here - i.e. the modeladmin interface is set up to edit an instance of B with several A's linked to it) and referenced by its field name rather than the related_name. Also, it should be the parent model that's defined as ClusterableModel, not the child:
class B(ClusterableModel):
aes = ParentalManyToManyField('A', blank=True)
edit_handler = TabbedInterface([
ObjectList([
FieldPanel('aes', widget=CheckboxSelectMultiple),
], heading=_('Aes')),
])
class A(Model): # doesn't need to be Orderable, because M2M relations don't specify an order
...

Related

wagtail search_fields on snippet with foreign key

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),
]

Using a Wagtail "ChoiceBlock" with dynamic choices rather than a hardcoded list

We have a setup with a Blog model that has a manytomany relation for BlogPageCategory, and we have a "recent blog posts" streamfield block that lets you specify whether to show cards for X latest blog posts, or X latest blog posts from a specific category.
As such, we started with the following code:
from wagtail.core import blocks
class RecentBlogEntries(blocks.StructBlock):
title = blocks.CharBlock(
required=True,
)
category_filter = blocks.ChoiceBlock(
label='Filter by Category',
required=False,
choices=[
('all', 'All'),
('First Category', 'First Category'),
('...',. '...'),
],
)
...
But hardcoding the categories is kind of silly, and being able to pick them from "what the list is, right now, based on the CMS data for BlogPageCategory" would be far more convenient. However, the following code (of course) turns into an equally hardcoded migration:
from wagtail.core import blocks
from ... import BlogPageCategory
class RecentBlogEntries(blocks.StructBlock):
title = blocks.CharBlock(
required=True,
)
choices = [ (cat.name, cat.name) for cat in BlogPageCategory.objects.all()]
choices.sort()
choices.insert(0, ('all', 'All'))
category_filter = blocks.ChoiceBlock(
label='Filter by Category',
required=False,
choices=choices,
)
...
Is there any way to make this a dynamic value instead of a list that is fixed by makemigrations?
ChoiceBlock accepts a callable function as the choices argument:
def get_categories():
return [(cat.name, cat.name) for cat in BlogPageCategory.objects.all()]
class RecentBlogEntries(blocks.StructBlock):
title = blocks.CharBlock(
required=True,
)
category_filter = blocks.ChoiceBlock(
label='Filter by Category',
required=False,
choices=get_categories,
)
The callable needs to be defined at the top level of a module so that the migration can make a reference to it (i.e. it can't be a method on a class), and if it gets subsequently moved or renamed, you'll need to edit the migration accordingly.

Using Django's CheckConstraint with annotations

I have a Django model where each instance requires a unique identifier that is derived from three fields:
class Example(Model):
type = CharField(blank=False, null=False) # either 'A' or 'B'
timestamp = DateTimeField(default=timezone.now)
number = models.IntegerField(null=True) # a sequential number
This produces a label of the form [type][timestamp YEAR][number], which must be unique unless number is null.
I thought I might be able to use a couple of annotations:
uid_expr = Case(
When(
number=None,
then=Value(None),
),
default=Concat(
'type', ExtractYear('timestamp'), 'number',
output_field=models.CharField()
),
output_field=models.CharField()
)
uid_count_expr = Count('uid', distinct=True)
I overrode the model's manager's get_queryset to apply the annotations by default and then tried to use CheckConstraint:
class Example(Model):
...
class Meta:
constraints = [
models.CheckConstraint(check=Q(uid_cnt=1), name='unique_uid')
]
This fails because it's unable to find a field on the instance called uid_cnt, however I thought annotations were accessible to Q objects. It looks like CheckConstraint queries against the model directly rather than using the queryset returned by the manager:
class CheckConstraint(BaseConstraint):
...
def _get_check_sql(self, model, schema_editor):
query = Query(model=model)
...
Is there a way to apply a constraint to an annotation? Or is there a better approach?
I'd really like to enforce this at the db layer.
Thanks.
This is pseudo-code, but try:
class Example(Model):
...
class Meta:
constraints = [
models.UniqueConstraint(
fields=['type', 'timestamp__year', 'number'],
condition=Q(number__isnull=False),
name='unique_uid'
)
]

MapField is not displayed in Django Rest Framework Mongoengine

I have a model with following attributes.
class File(DynamicDocument):
country = fields.StringField(max_length=100, unique=True)
languages = fields.MapField(fields.MapField(
fields.EmbeddedDocumentField(AudioImage)))
I am trying to use Django Rest Framework Mongoengine as follows:
from rest_framework_mongoengine.serializers import DocumentSerializer
class TestSerializer(DocumentSerializer):
class Meta:
model = File
It simply gives the following output:
But I wanted it to address the tree like structure with all the fields from AudioImage class as well.
Did I miss anything? or There is another way for MapField ?
Sijan, is it correct that you want your File documents to have the following structure:
{
"country": "UK",
"languages": {
"hindi": AudioImageJSON,
"russian": AudioImageJSON,
"cockney": AudioImageJSON
}
}
where the structure of AudioImageJSON is described by corresponding EmbeddedDocument?
In that case, your DocumentSerializer is correct and your specify your model as follows:
class AudioImage(EmbeddedDocument):
content = fields.FileField()
class File(DynamicDocument):
country = fields.StringField(max_length=100, unique=True)
languages = fields.MapField(fields.EmbeddedDocumentField(AudioImage))
Note that Browsable API won't be able to display nested form inputs for EmbeddedDocument fields. But you may still use raw data view.

Wagtail CMS: Finding all pages that have a relation to the current page

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