Please help me understand Django GenericInlineFormSet - django

Django ContentTypes provides a GenericInlineFormSet, however the documentation does not explain how to use it, except for this test, which doesn't really explain it in a way I understand.
Please can you help me understand it?
Let's say I have the following classes
class Dog(models.Model):
name = models.CharField(max_length=64)
breed = models.CharField(blank=True, max_length=64)
class Meta:
verbose_name = 'Dog'
class Fish(models.Model):
name = models.CharField(max_length=64)
habitat = models.CharField(blank=True, max_length=64)
class Meta:
verbose_name = 'Fish'
class Pet(models.Model):
content_type = models.ForeignKey(
ContentType,
limit_choices_to={'model__in':('dog', 'fish')},
verbose_name='Species'
)
object_id = models.CharField(max_length=64, verbose_name='Animal')
object = generic.GenericForeignKey('content_type', 'object_id')
owner = models.ForeignKey(Owner)
class Meta:
unique_together = [("content_type", "object_id")]
What does the view look like to display a form for a Pet?

GenericInlineFormSet works just like a standard inline formset, except that it uses generic relations rather than standard foreign keys.

Related

Override Field Option on Inherited Model in Django

I've found similar questions and answers, but none seems exactly right. I've got an abstract base model like so:
class BaseModel(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
description = models.CharField(max_length=512, blank=True, null=True, help_text='Help')
class Meta:
abstract = True
And then I'm inheriting from it:
class AnotherModel(BaseModel):
field1 = models.CharField(max_length=512)
But I want this model's help_text on the description field to be something else, like help_text='Some other help text'
What's the best way to do this? Can I override options on fields from inherited models?
If this is really about the help text, I suggest to just override the ModelForm. However, you can use a factory and return the inner class:
def base_factory(description_help: str = "Standard text"):
class BaseModel(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
description = models.CharField(
max_length=512, blank=True, null=True, help_text=description_help
)
class Meta:
abstract = True
return BaseModel
class ConcreteModel(base_factory("Concrete help")):
field1 = ...

Nested Query / not directly related field in Django

If the models are as follows,
class Subject(BaseModel):
name = models.CharField(db_column='name', max_length=200, blank=False, null=False, unique=True)
class Meta:
managed = True
db_table = 'Subject'
class Topic(BaseModel):
name = models.CharField(db_column='name', max_length=200, blank=False, null=False, unique=True)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE, null=False, related_name='subject_topic')
class Meta:
managed = True
db_table = 'Topic'
class Question(BaseModel):
topic = models.ForeignKey(Topic, on_delete=models.CASCADE, null=False, related_name='question_topic')
class Meta:
managed = True
db_table = 'Question'
How can I make a query Question for a subject.
questions = Question.objects.filter(topic_in=Topic.objects.get(subject=subject).only('id').all())
but it's not working. Any help would be really great help.
Your current "inner queryset" won't give you multiple values, it only returns 1.
Topic.objects.get(subject=subject).only('id').all()
You are using .objects.get() which returns a model instance, so for example .only() or .all() on that won't work unless your model has such a defined function.
Furthermore you don't really need 2 querysets, you can get your result with only one:
my_subject_ids = [...] # these are the ids of Subject models you want
Question.objects.filter(topic__subject_id__in=my_subject_ids)
You can also query for Subject.name, just use the following for this: topic__subject__name__in=my_list.
If you want to query for a specific Subject-instance you can use topic__subject=my_obj.
You also might wanna take a look at the docs for more information on that.

Related Name on Base Model Object

I have a base model like this:
class FileAttachment(models.Model):
attached_file = models.FileField(upload_to=generate_upload_path, default=None)
author = models.ForeignKey(Account, related_name='attachments', default=None)
class Meta:
abstract = True
I inherit it on two different models, for two different places you can attach files like this:
class DeliverableFileAttachment(FileAttachment):
deliverable = models.ForeignKey(Deliverable, related_name='files')
class TaskItemFileAttachment(FileAttachment):
taskitem = models.ForeignKey(TaskItem, related_name='files')
for the foreign key to author, is it okay that two different models have the same related name to account? And is there a better way to do this ?
EDIT:
What about the opposite situation:
class DeliverableFileAttachment(FileAttachment):
deliverable = models.ForeignKey(Deliverable, related_name='files')
class TaskItemFileAttachment(FileAttachment):
taskitem = models.ForeignKey(TaskItem, related_name='files')
Can the related names be the same since they point to different models?
Or say I have an object like this:
class FileAttachment(models.Model):
author = models.ForeignKey(Account, related_name='files', default=None)
attached_file = models.FileField(upload_to=generate_image_upload_path, default=None)
limit = models.Q(app_label='workflow', model='project') | \
models.Q(app_label='workflow', model='taskitem') | \
models.Q(app_label='workflow', model='deliverable')
content_type = models.ForeignKey(
ContentType,
verbose_name=_('File Attachment'),
limit_choices_to=limit,
null=True,
blank=True,
related_name='files'
)
object_id = models.PositiveIntegerField(
verbose_name=_('related object'),
null=True,
)
content_object = GenericForeignKey('content_type', 'object_id')
Is it okay that the related_name to author and the content_type are the same?
In an abstract class you shall not hardcode the related_name but do something like this:
class Base(models.Model):
...
field= models.ForeignKey(
OtherModel,
related_name="%(app_label)s_%(class)s_related",
)
See the docs.

Generic Relation Constraints in Django

I want the a counterpart of Tag (BlogPost) to have at least 1 instance of Tag or it shouldn't be created. (same effect like null=False). I tried a lot but couldn't figure out to apply these contrains. Any ideas?
class Tag(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
text = models.CharField("text", max_length=255)
class Meta:
unique_together = ('content_type', 'object_id', 'text',)
class BlogPost(models.Model):
title = models.CharField("title", max_length=255)
tags = generic.GenericRelation(Tag, verbose_name="tags")
class TagInline(generic.GenericTabularInline):
model = Tag
extra = 1
class BlogPostAdmin(admin.ModelAdmin):
inlines = (TagInline,)
If you want this in the form of a Database constraint, then I'm not sure that such a thing exists.
Otherwise I would go with overriding the clean( self ) function on your model.
This can be used for custom validation.
def clean( self ):
# validate that this model has one or more tag

Verbose name for admin model Class in django

Is it possible to create a verbose name for the actual Class model?
class User(models.Model):
fname = models.CharField(max_length=50, verbose_name = 'first name')
So in the admin panel it will be referenced by its verbose name and not 'user' ?
class User(models.Model):
fname = models.CharField(max_length=50, verbose_name = 'first name')
class Meta:
verbose_name = "users"
Source: https://docs.djangoproject.com/en/2.1/topics/db/models/#meta-options
verbose_name and verbose_name_plural both the properties of Meta class are very important to modify the default behaviour of Django to display the name of our models on Admin interface.
You can change the display of your model names using on Admin Interface using verbose_name and verbose_name_plural properties and model fields names using keyword argument verbose_name.
Please find the below 2 examples.
Country model:
class Country(models.Model):
name = models.CharField(max_length=100, null=False, blank=False, help_text="Your country", verbose_name="name")
userid = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"Country {str(self.id)} - {self.name}"
class Meta:
verbose_name = "Country"
verbose_name_plural = "Countries"
If you will not specify verbose_name_plural then Django will take it as Countrys which does not look good as we want it as Countries.
This better fits in the following type of Model.
Gender model:
class Gender(models.Model):
name = models.CharField(max_length=100, null=False, blank=False, help_text="Gender", verbose_name = "name")
userid = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"Gender {str(self.id)} - {self.name}"
class Meta:
verbose_name = "Gender"
You could add a "verbose_name_plural" to the "Meta" class too.
To alter admin model without polluting the model itself, you can utilize a proxy admin model, like this:
# admin.py
from . import models
class Users(models.User):
class Meta:
proxy = True
class UsersAdmin(admin.ModelAdmin):
...
admin.site.register(Users, UsersAdmin)
ConfigAbility._meta.verbose_name = 'config ability'
ConfigAbility._meta.verbose_name_plural = 'config ability'
I did explore this, but don't know whether it's the thing you need. I put those codes in class ConfigAbilityAdmin in Admin.py. Then, the result:
enter image description here
With this approach, you don't need to config Meta method in model class, especially when model class's code is generated from inspectdb...