I have 2 models with a through table such as:
class A(models.Model):
title = models.CharField(max_length=500)
bs = models.ManyToManyField(to='app.B', through='app.AB', blank=True)
content_panels = [
FieldPanel('title'),
FieldPanel('fields'), # what should go here?
]
class AB(models.Model):
a = models.ForeignKey(to='app.A')
b = models.ForeignKey(to='app.B')
position = models.IntegerField()
class Meta:
unique_together = ['a', 'b']
I'm getting the following error when trying to save:
Cannot set values on a ManyToManyField which specifies an intermediary model.
That error makes sense to me. I should save AB instances instead. I'm just unsure what's the best way to achieve that in Wagtail.
What you need is an InlinePanel: http://docs.wagtail.io/en/v2.0.1/getting_started/tutorial.html#images
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel
from wagtail.core.models import Orderable
from modelcluster.fields import ParentalKey
# the parent object must inherit from ClusterableModel to allow parental keys;
# this happens automatically for Page models
from modelcluster.models import ClusterableModel
class A(ClusterableModel):
title = models.CharField(max_length=500)
content_panels = [
FieldPanel('title'),
InlinePanel('ab_objects'),
]
class AB(Orderable):
a = ParentalKey('app.A', related_name='ab_objects')
b = models.ForeignKey('app.B')
panels = [
FieldPanel('b'),
]
Related
I have the following models in my models.py file in my django project
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.conf import settings
class CustomUser(AbstractUser):
pass
# add additional fields in here
class PDFForm(models.Model):
pdf_type=models.IntegerField(default=0)
pdf_name=models.CharField(max_length=100,default='')
file_path=models.FileField(default='')
class FormField(models.Model):
fk_pdf_id=models.ForeignKey('PDFForm', on_delete=models.CASCADE,default=0)
field_type=models.IntegerField(default=0)
field_page_number=models.IntegerField(default=0)
field_x=models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_y=models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_x_increment=models.DecimalField(max_digits=6,decimal_places=2,default=0)
class Meta:
ordering= ("field_page_number", "field_type")
class UserData(models.Model):
fk_user_id=models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,default=0)
field_type=models.IntegerField(default=0)
field_text=models.CharField(max_length=200,default='')
field_date=models.DateField()
Here is how the models are related
1) a pdfform contains a pdf form and path for it on the file system
2) A pdfform has multiple FormFields in it. Each field has attributes, and the specific one under discussion is field_type
3)The UserData model has user's data, so one User can have multiple rows in this table. This model also has the field_type column.
What I am trying to query is to find out all rows present in the Userdata Model which are present in the FormField Model ( matched with field_type) and that are of a specific PDFForm.
Given that the Many to Many relationship in django models cannot happen between no unique fields, how would one go about making a query like below
select a.*, b.* from FormField a, UserData b where b.fk_user_id=1 and a.fk_pdf_id=3 and a.field_type=b.field_type
I have been going through the documentation with a fine toothed comb, but obviously have been missing how django creates joins. what is the way to make the above sql statement happen, so I get the required dataset?
I think UserData is missing a relation to FormField, but if you had this relation you could do:
UserData.objects.filter(
fk_user_id=1, # Rename this to user, Django wilt automicly create a user_id column
form_field__in=FormField.objects.filter(
fk_pdf_id=<your pdfid> # same as fk_user_id
)
)
Edit updated models
When you use a ForeignKey you don't have to specify the _id or default=0, if you don't always want to fill the field its better to set null=True and blank=True
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.conf import settings
class CustomUser(AbstractUser):
pass
# add additional fields in here
class FieldTypeMixin:
TYPE_TEXT = 10
TYPE_DATE = 20
TYPE_CHOISES = [
(TYPE_TEXT, 'Text'),
(TYPE_DATE, 'Date'),
]
field_type=models.IntegerField(default=TYPE_TEXT, choises=TYPE_CHOISES)
class PDFForm(models.Model):
pdf_type = models.IntegerField(default=0)
pdf_name = models.CharField(max_length=100,default='')
file_path = models.FileField(default='')
class FormField(models.Model, FieldTypeMixin):
pdf_form = models.ForeignKey('PDFForm', on_delete=models.CASCADE)
field_page_number = models.IntegerField(default=0)
field_x = models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_y = models.DecimalField(max_digits=6,decimal_places=2,default=0)
field_x_increment = models.DecimalField(max_digits=6,decimal_places=2,default=0)
class Meta:
ordering = ("field_page_number", "field_type")
class SubmittedForm(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE)
pdf_form = models.ForeignKey(PDFForm, models.CASCADE)
class SubmittedFormField(models.Model, FieldTypeMixin):
submitted_form = models.ForeignKey(SubmittedForm, models.CASCADE)
form_field = models.ForeignKey(FormField, models.CASCADE, related_name='fields')
field_text = models.CharField(max_length=200,default='')
field_date = models.DateField()
class Meta:
unique_together = [
['submitted_form', 'form_field']
]
I have a model containing a ForeignKey to another model. I am attempting to serialize this model and want to control what field is returned for the foreignkey field. See below:
models.py
class Surveyor(models.Model):
num = models.CharField(max_length=3)
name = models.CharField(max_length=250)
class Anblsrecord(models.Model):
...
sur_num = models.ForeignKey(Surveyor, on_delete=models.CASCADE)
views.py
def anbls_points(request):
points_as_geojson = serialize('geojson', Anblsrecord.objects.all()[:5], fields=(... 'sur_num'))
return JsonResponse(json.loads(points_as_geojson))
When I view this I get:
... "sur_num": 1 ...
where the "1" is "num" from Surveyor class. I want to return "name".
I looked at https://docs.djangoproject.com/en/2.2/topics/serialization/ which talks about multi-table inheritance, but I can't find anything for a related table.
Any help would be greatly appreciated.
Django Rest Framework serializers with django-rest-framework-gis worked:
serializers.py
from anblsrecords import models
from rest_framework_gis.serializers import GeoFeatureModelSerializer
class AnblsrecordSerializer(GeoFeatureModelSerializer):
sur_name = serializers.CharField(source='sur_num.name')
class Meta:
model = models.Anblsrecord
geo_field = "geom"
fields = (
...
'sur_name',
)
views.py
from rest_framework import generics
class ListAnbls_points(generics.ListCreateAPIView):
queryset = Anblsrecord.objects.all()[:5]
serializer_class = serializers.AnblsrecordSerializer
This returns:
"properties": {
...,
"sur_name": "Name of Surveyor",...}, and includes the geometry feature.
I am trying to set make a footer for a Wagtail site that is included on every page, but I want to include a list of links (phone, email, social media). If I try the code below without the panel = [...] I can see it sort of works, but I am unable to add any items:
from wagtail.contrib.settings.models import BaseSetting, register_setting
from django import forms
class ContactInfo(models.Model):
CONTACT_CHOICES = (
('fas fa-phone', 'Phone'),
('fas fa-envelope', 'Email'),
('fab fa-facebook-f', 'Facebook'),
('fa-instagram', 'Instagram'),
('fab fa-linkedin', 'LinkedIn'),
('fab fa-twitter', 'Twitter'),
('fab fa-pinterest', 'Pinterest'),
('fab fa-github', 'GitHub'),
('fab fa-gitlab', 'GitLab'),
)
contact_type = models.CharField(choices=CONTACT_CHOICES, max_length=50)
contact_info = models.CharField(max_length=50)
info_prefix = models.CharField(max_length=10, editable=False)
def save(self, *args, **kwargs):
if self.contact_type == 'Phone':
self.info_prefix = 'tel:'
elif self.contact_type == 'Email':
self.info_prefix = 'mailto:'
else:
self.info_prefix = ''
#register_setting
class Contact(BaseSetting):
contact = models.ManyToManyField(ContactInfo)
panels = [
FieldPanel('contact', widget=forms.CheckboxSelectMultiple)
]
Is there a to add items to the M2M field? Is there a way to make lists of items in the Wagtail settings? Is there an easier way to make a footer that automatically is rendered on every page?
Each ContactInfo item (presumably) belongs to a single Contact, so this is a one-to-many relation rather than many-to-many. (A many-to-many relation in this case would mean that you have a shared pool of ContactInfo items previously defined through some other view, and you're selecting which ones to attach to the current Contact.)
In Wagtail, this would be defined using a ParentalKey on ContactInfo to point to the corresponding Contact, and rendered with an InlinePanel. (See the gallery image example from the Wagtail tutorial for an example.)
from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel
from wagtail.core.models import Orderable
from wagtail.contrib.settings.models import BaseSetting, register_setting
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
class ContactInfo(Orderable):
CONTACT_CHOICES = (
# ...
)
contact = ParentalKey('Contact', on_delete=models.CASCADE, related_name='contact_links')
contact_type = models.CharField(choices=CONTACT_CHOICES, max_length=50)
contact_info = models.CharField(max_length=50)
# info_prefix handling omitted here for brevity
panels = [
FieldPanel('contact_type'),
FieldPanel('contact_info'),
]
#register_setting
class Contact(BaseSetting, ClusterableModel):
panels = [
InlinePanel('contact_links', label="Contact")
]
In a Wagtail-based site, I have an ArticlePage model which is related to an Author snippet model in a many-to-many relationship like this:
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.core.models import Orderable, Page
from wagtail.search import index
from wagtail.snippets.models import register_snippet
class ArticlePage(Page):
search_fields = Page.search_fields + [
index.FilterField('author_id'),
]
#register_snippet
class Author(models.Model):
name = models.CharField(max_length=255, blank=False)
class ArticleAuthorRelationship(Orderable, models.Model):
author = models.ForeignKey('Author', on_delete=models.CASCADE, related_name='articles')
page = ParentalKey('ArticlePage', on_delete=models.CASCADE, related_name='authors')
I want to be able to search ArticlePages and filter them by a particular Author, which I can do like this:
author = Author.objects.get(id=1)
articles = ArticlePage.objects.live() \
.filter(authors__author=author) \
.search('football')
But in the dev server logs I get this warning:
articles.ArticlePage: ArticlePage.search_fields contains non-existent field ‘author_id’
My question is: Am I doing this right? It works, but the warning makes me think there might be a more correct way to achieve the same result.
I have also tried using FilterField('authors'), which stops the warning, but I can't work out how to filter on that.
the correct way was explained in the documentation
here an example that is shared in the official documentation:
from wagtail.search import index
class Book(models.Model, index.Indexed):
...
search_fields = [
index.SearchField('title'),
index.FilterField('published_date'),
index.RelatedFields('author', [
index.SearchField('name'),
index.FilterField('date_of_birth'),
]),
]
so in your case, you should write:
class ArticlePage(Page):
search_fields = Page.search_fields + [
index.RelatedFields(
"author", [index.SearchField("id")]
),
]
let me know if it is working
Using Wagtail I have ArticlePages, each of which can have 0 or more Authors, which are Snippets.
I'd like to get a list of Authors with the number of ArticlePages they're attached to, but can't work out how. I'm getting confused by ModelCluster I think, as I'd be fine with vanilla Django.
(I'm not even sure if I'm over-complicating this; I don't need the Authors to be orderable on the Articles...)
from django.db import models
from modelcluster.fields import ParentalKey
from wagtail.admin.edit_handlers import InlinePanel
from wagtail.core.models import Orderable, Page
from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.snippets.models import register_snippet
class ArticlePage(Page):
content_panels = Page.content_panels + [
InlinePanel('authors', label='Authors'),
]
#register_snippet
class Author(models.Model):
name = models.CharField(max_length=255, blank=False)
panels = [
FieldPanel('name'),
]
class ArticleAuthorRelationship(Orderable, models.Model):
author = models.ForeignKey(
'Author',
on_delete=models.CASCADE,
related_name='+')
page = ParentalKey(
'ArticlePage',
on_delete=models.CASCADE,
related_name='authors')
panels = [
SnippetChooserPanel('author'),
]
FWIW, I know I could get a single Author's articles using article.get_usage(), which is handy!
Coming back to this a day later, I think the only issue is having set related_name on ArticleAuthorRelationship's author property to '+' (which means there's no backwards relationship from Author to this model).
If I instead do:
class ArticleAuthorRelationship(Orderable, models.Model):
author = models.ForeignKey(
'Author',
on_delete=models.CASCADE,
related_name='articles') # Changed this to 'articles'
# etc.
Then I can do this quite easily:
from django.db.models import Count
from my app.models import Author
authors = Author.objects.annotate(num_articles=Count('articles')).order_by('-num_articles')
Then each item in the authors QuerySet has a num_articles property.
I usually set a related_name but this seems less common in the Wagtail docs, and I almost hadn't noticed I'd copied that behaviour here, so this threw me.
If anyone's reading this and can give examples of why/when you'd want to set related_name to '+', I'd be interested to know. Thanks.