Django Forms for generic relationships. How to include them? - django

I have a Phone model that is being constantly used by many different models as a generic relationship. I have no idea how to include it in the Create/Update forms for those models… how good or bad of an idea is it to include the extra fields in a forms.ModelForm subclass… kind of like this:
###### models.py
class UpstreamContactModel(models.Model):
client = models.ForeignKey(UpstreamClientModel,
related_name='contacts')
contact_type = models.CharField(max_length=50, default='Main',
blank=True, null=True)
name = models.CharField(max_length=100, unique=True)
job_title = models.CharField(max_length=50, blank=True, null=True)
email = models.EmailField(blank=True, null=True)
skype_id = models.CharField(max_length=30, blank=True, null=True)
phones = generic.GenericRelation(Phone)
notes = models.TextField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
verbose_name = 'Contact'
class Phone(models.Model):
info = models.CharField('Eg. Office, Personal, etc',
max_length=15, blank=True)
number = models.CharField('Phone numbes', max_length=20)
# generic relationships so I can attach to other objects later on
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
return self.number
##### forms.py
class ContactForm(forms.ModelForm, BaseValidationForm):
info = forms.CharField(max_length=15)
number = forms.CharField(max_length=20)
class Meta:
model = UpstreamContactModel
def clean(self):
???
def save(self):
???
I've been trying to find out how people handles CRUD when a generic relationship is involved but I've been unsuccessful at that so far.

If you use a model form you can see what kind of form elements they use.
For content_type it will typically use a ModelChoiceField with ContentType.objects.all() as the queryset, and object_id will be a TextInput looking for the positive integer. I don't think the actual generic accessor will show up in the form if you use a model form.
If you want a more elegant solution than this I'd look into writing a custom form field or widget to handle it.
I don't think it's bad practice to add additional fields to the ModelForm as you have, in fact I think that's a good route to go about it. Overriding the save method will probably give you the functionality you desire.

Related

Django - ForeignKey Filter Choices

I'd like to filter the choices that a user can choose in my ForeignKey Field.
I basically have a ForeignKey for the subject of the Test and the actual topic of the Test. These topics come from a different model and are linked to a subject. Now I'd like to filter the choices to only include the topics that are linked to the currently selected subject. Is that possible and if so, how?
models.py
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True)
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
class Thema(models.Model):
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.CharField(max_length=50)
class Subject(models.Model):
teacher = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
name = models.CharField(max_length=20)
The Problem if I use this:
# thema model #staticmethod
def return_thema(subject):
themen = Thema.objects.filter(subject=subject)
return {'thema': themen}
#test model
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True,limit_choices_to=Thema.return_thema(subject))
Is that I get the Error:
django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet.
Meaning I can't get the objects of the Thema Model while the models are loading
EDIT (for Swift):
That seemed to resolve the error when trying to makemigrations, but I now get this error, when visiting the admin portal to create a new Test:
File "/Users/di/Code/Schule/GymnasiumApp/venv/lib/python3.10/site-packages/django/db/models/sql/query.py", line 1404, in build_filter
arg, value = filter_expr
ValueError: too many values to unpack (expected 2)
I think what you are looking for ideally would be ForeignKey.limit_choices_to
Please see the docs:
https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to
You can limit the choices available at a model level, which is enforced throughout the django app, including forms automatically.
Edit because OP provided more information
Ok so I believe if you declare the thema field on the test model like so, it will solve the issue, and I will explain why after:
class Test(models.Model):
student = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, blank=True, null=True)
thema = models.ForeignKey(Thema, on_delete=models.CASCADE, blank=True, null=True, limit_choices_to=Q('thema_set__subject_set'))
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE, blank=True, null=True)
grade = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(6)], blank=True, null=True)
date = models.DateField(default=datetime.date.today)
def save(self, *args, **kwargs):
if not self.school_class and self.student:
self.school_class = self.student.klasse
return super().save(*args, **kwargs)
We are essentially telling Django to evaluate the relationship between the limited choices "lazily" I.e. when the form is loaded dynamically. Django forms will look at the limit_choices_to field argument and apply it to the available choices.
I'm not 100% about the relationship of your models so the Q(...) I added, might actually need to be Q('subject_set')
If you use django forms you can use the model choice field.
In your view you can set your queryset of this choicefield. Zo you can filter it.
fields['your model field'].queryset = yourmodel.objects.filter(your filter parameters)
I think there is also problem in save method also. Aren't you need to write the name of the model inside like
return super(<modelName>).save(*args, **kwargs)

DJANGO many to one

I need your help, is really basic.
I have two models, Autor and Post with a many to one relationship. I'm having problems retrieving the data in the html page. What I want to do is list all the posts for a specific Autor within a FOR and besides that I need to show the first name and last name of the autor out of the FOR.
I really appreciate your help.
class Autor(models.Model):
first_name = models.CharField(max_length=30, null=False, verbose_name='First Name')
last_name = models.CharField(max_length=30, null=False, verbose_name='Last Name')
def __str__(self):
return self.first_name
class Meta:
db_table = 'autor'
verbose_name = 'Autor'
verbose_name_plural = 'Autors'
ordering = ['id']
class Post(models.Model):
autor = models.ForeignKey(Autor, on_delete=models.CASCADE)
post = models.CharField(max_length=200, null=False, verbose_name='Post')
def __str__(self):
return self.post
class Meta:
db_table = 'post'
verbose_name = 'Post'
verbose_name_plural = 'Posts'
ordering = ['id'] ```
Since you don't specify a related_name attribute in ForeignKey definition, Django automatically creates a related_name using the name of your model with the suffix _set, for example, if sample_author is an instance of Author, this query returns all posts of the sample_author:
author_posts = sample_author.post_set.all()
then you can use all posts that exist in the result of this query within a FOR loop.
for post in author_posts:
do something
outside of this loop, you can access to first_name and last_name of the sample_author instance by sample_author.first_name and sample_author.last_name
.

Check if an object exists in another models foreignkey field

I have a reviewing system for Subjects. User can review a subject, and users can like that review.
class UserReview(models.Model):
subject = models.ForeignKey(Subject, blank=True, null=True)
user = models.ForeignKey(User)
review = models.TextField(null=True, blank=True)
likes = GenericRelation('Like')
class Like(models.Model):
user = models.ForeignKey(User)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
unique_together = (('content_type', 'object_id', 'user'),)
How do I check if a user (eg UserA) exist in the likes of a user review? For now I am doing this way:
if user_review_obj.likes.filter(user=UserA).count() > 0:
// exist...
But I am hoping there is another better way.
You can use exists, however the correct usage is
if user_review_obj.likes.filter(user=UserA).exists():
# . . .

Create relationship between two models using django class based views

I have two models Company and Campaign. I need to create a relationship between them. I think my models are fine.
companies/model.py
class Company(models.Model):
class Meta:
verbose_name_plural = "companies"
user = models.ForeignKey(settings.AUTH_USER_MODEL)
title = models.CharField(blank=False, max_length=128, default='')
slug = models.SlugField(blank=True, unique=True)
archived = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
campaigns/models.py
class Campaign(models.Model):
class Meta:
verbose_name_plural = "campaigns"
company = models.ForeignKey('companies.Company', on_delete=models.CASCADE,)
title = models.CharField(blank=False, max_length=128, default='')
slug = models.UUIDField(default=uuid.uuid4, blank=True, editable=False)
def __str__(self):
return str(self.title)
campaigns/forms.py
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
fields = ['title','description','archived']
campaigns/views.py
class CampaignCreateView(SubmitBtnMixin, CreateView):
model = Campaign
company = None
form_class = CampaignForm
submit_btn = "Add Campaign"
template_name = "form.html"
campaigns/urls.py
url(r'^campaign/create/$', CampaignCreateView.as_view(), name='campaign-create'),
My question is, when creating a new campaign, where and how do I pick up the Company pk to populate the Campaign model? What is the most secure and best practice for doing this?
I found a solution but would like input on best practices still.
I added this to my CampaignCreateView
def form_valid(self, form):
company = get_object_or_404(Company, id=self.kwargs.get('pk'), user_id=self.request.user.id)
form.instance.company_id = company.id
return super(CampaignCreateView, self).form_valid(form)
and I changed my url to:
url(r'^campaign/(?P<pk>\d+)/create/$', CampaignCreateView.as_view()...
Not sure that I like the pk in the URL since it can be jacked. This is why I am filtering on the userid at the company model to make sure that the data is coming from the owner.
I thought of doing this by registering the company in the session id but I am not convinced that sessions do not present their own problems.

Accessing a field via a recursive ManyToMany relationship in a Django model

Given the following model:
class Project(models.Model):
project_name = models.CharField(max_length=255)
abstract = models.TextField(blank=True, null=True)
full_description = models.TextField(blank=True, null=True)
date_begun = models.DateField(blank=True, null=True)
related_projects = models.ManyToManyField('self', blank=True, null=True)
class Meta:
ordering = ['project_name']
def __unicode__(self):
return self.project_name
How do I access the ID of projects references in the related_projects field. For example, I can get their project_name values by doing something like this:
def transform_related_projects(self, instance):
return [unicode(rp) for rp in instance.related_projects.all()]
But I can't see how to get the if for the Project record, since the def unicode(self) function only return the project name as a unicode string. I know I'm missing something obvious. Thanks
def transform_related_projects(self, instance):
return [rp.id for rp in instance.related_projects.all()]