How can you add a many to many field in Wagtail Admin? - django

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

Related

How to query by joining a Django Model with other, on a non unique column?

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']
]

Wagtail: Filtering a search on a ManyToMany through model

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

Django, Wagtail admin: handle many to many with `through`

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

Custom content type appears with an empty option in the admin "add type of page" page in mezzanine

I am doing a small blog in Mezzanine for learning purpose and wanted to add a custom content type by sub-classing "mezzanine.pages.models.Page" and registering this model with admin. My classes look something like this:
models.py:
from django.db import models
from mezzanine.pages.models import Page
class Student(Page):
dob = models.DateField("Date of birth")
name = models.CharField("Name", max_length=30)
gender = models.CharField("Gender", max_length = 5, choices=(('M','Male'),
('F','Female')), default = 'M')
image = models.ImageField(upload_to='Students')
class Project(models.Model):
student = models.ForeignKey("Student")
project_image = models.ImageField(upload_to="StudentProjects")
admin.py:
from copy import deepcopy
from django.contrib import admin
from mezzanine.pages.admin import PageAdmin
from .models import Student, Project
student_extra_fieldsets = ((None, {"fields": ("dob","name","gender","image")}),)
class ProjectInline(admin.TabularInline):
model = Project
class StudentAdmin(PageAdmin):
inlines = (ProjectInline,)
fieldsets = deepcopy(PageAdmin.fieldsets) + student_extra_fieldsets
admin.site.register(Student, StudentAdmin)
Now, when I visit "http://localhost:8000/admin/pages/page/" to add my newly registered content type, I get an empty option with no name, but when I select I get the Custom Content type "Student" Page to add and edit.
Since I have just started with Django and Mezzanine, I cannot simply figure it out.
I am using "sqlite" as backend and not using "South"
Any pointers to this??
Thanx for your help :)
I removed that "*name = models.CharField("Name", max_length=30)*" in models.py and replaced it with:
first_name = models.CharField("First Name", max_length=30)
last_name = models.CharField("Last Name", max_length=30)
And now every thing seems OK!!. (Only if I used "South"

Django app to retrieve data from other apps models?

I have an app name sync which has a form created from a model that saves itself. I want to create another app called activity that retrieves the data from the sync models and other future apps. How can I do that in the activity views app?
This is my sync models.py
from django.db import models
from django.contrib.auth.models import User
from django.forms import ModelForm
FS_CHOICES = (
('/path1/', 'P1'),
('/path2/', 'P2'),
('/path3/', 'P3'),
)
OPTIONS = (
('-n', 'TRY'),
)
class SyncJob(models.Model):
date = models.DateTimeField()
user = models.ForeignKey(User, unique=False)
source = models.CharField(max_length=3, choices=FS_CHOICES)
destination = models.CharField(max_length=3, choices=FS_CHOICES)
options = models.CharField(max_length=10, choices=OPTIONS)
class SyncJobForm(ModelForm):
class Meta:
model = SyncJob
fields = ['source', 'destination', 'options']
Ok, in activity views.py I have this:
from toolbox.sync.models import SyncJob
from django.shortcuts import render_to_response
def Activity()
sync_job = SyncJob.objects.get(id=03)
return render_to_response('base.html', {'sync_job': sync_job})
UPDATE: When I try to view the page it displays the error:
'function' object is not iterable
Just import it like any other python class.
So in your activity app you'd do something like this:
from sync.models import SyncJob
sync_job = SyncJob.objects.get(id=99)