Related Name on Base Model Object - django

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.

Related

Constraining instantiation of a regular class based on foreginkeys to a regular class and an abstract class

I am creating my first django project and after having created a model A I realize that I want to create other models B, C ... that have certain traits in common with A but which needs to be separat models. Thus I created an abstract class 'InteractableItem' with these traits.
I want users to be able to like interactable items, however I also want to constrain the instantiation of a 'Like' model such that each user only can like a given interactable item once. To solve this I tried creating a models.UniqueConstraint in the like model between the fields 'user' and 'interactableitem'. This gave me the following error
ERRORS:
feed.Like.interactableitem: (fields.E300) Field defines a relation with model 'InteractableItem', which is either not installed, or is abstract.
feed.Like.interactableitem: (fields.E307) The field feed.Like.interactableitem was declared with a lazy reference to 'feed.interactableitem', but app 'feed' doesn't provide model 'interactableitem'.
I realise that my error is the referencing of an abstract class through a ForeignKey, however I dont see a way to constrain the instantiation of the like if the 'user' liking and the 'interactableitem' being like are not both fields in 'like'. This is where I need help.
How do you establish an instantiation constraint on such a 'Like' model?
Here I provide my referenced models:
class InteractableItem(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
date_update = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
class Meta:
abstract = True
class Like(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
interactableitem = models.ForeignKey(InteractableItem, on_delete=models.CASCADE, null=True, blank=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'interactableitem'], name='user_unique_like'),
]
After having tested different ways to constrain the instatiation of the 'like' model I realised that there was a way around this.
I let the abstract class 'BaseItem' have a ForeignKey to a regular class 'interactableitem'. 'Like' now has a models.Uniqueconstrait with two ForeignKey fields to regular classes which works. This way I just have to makesure that interactableitem is only created once, I'm sure there are better ways of doing this but this if self.pk should work for all scenarios I can think of, as it checks if the item is in the database before creation.
class InteractableItem(models.Model):
pass
class SomeItem(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
date_update = models.DateTimeField(auto_now=True)
interactableitem = models.ForeignKey(InteractableItem, on_delete=models.CASCADE, null=True, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if not self.pk:
self.interactableitem = InteractableItem.objects.create()
super(SomeItem, self).save(*args, **kwargs)
class Like(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
interactableitem = models.ForeignKey(InteractableItem, on_delete=models.CASCADE, null=True, blank=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'interactableitem'], name='user_unique_like'),
]

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 = ...

Django: Is it possible to limit ContentType by base Parent?

I have an Abstract base class "Parent" from which derive two subclasses "Child1" and "Child2". Each child can have a set of "Status".
I used "ContentType", "GenericForeignKey" and "GenericRelation" like so:
from django.db import models
from django.contrib.contenttypes.generic import GenericRelation, GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Parent(models.Model):
name = models.CharField(max_length=30, blank=True)
class Meta:
abstract = True
def __str__(self):
return self.name
class Child1(Parent):
id_camp = models.PositiveIntegerField()
config_type = models.CharField(max_length=30)
status_set = GenericRelation(Status)
class Child2(Parent):
temperature = models.FloatField(null=True, blank=True)
status_set = GenericRelation(Status)
class Status(models.Model):
code = models.CharField(max_length=10, null=True, blank=True)
message = models.CharField(max_length=100, null=True, blank=True)
content_type = models.ForeignKey(ContentType, limit_choices_to={'name__in': ('child1', 'child2',)}, null=True, blank=True)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
The actual solution works fine, but right now the limitation of choices of content type is by "name" and eventually I will create more subclacess of Parent later. I would like to replace limit_choices_to={'name__in': ('child1', 'child2',)} with somthing like limit_choices_to children of parent is there any straightforward way ?
limit_choices_to also accepts callables, so yes, a dynamic value should be possible:
Either a dictionary, a Q object, or a callable returning a dictionary or Q object can be used.
So something along these lines should work:
def get_children():
return {'model__in': [c.__name__ for c in Parent.__subclasses__()]}
and then later ...
limit_choices_to=get_children
Or even in one line:
limit_choices_to={'model__in': [c.__name__ for c in Parent.__subclasses__()]}

Mode Inheritance how to implement ModelForms (Inerithance?)

i'm recently moving my db to a model Inheritance structure. Here an example:
Task model
STATUS_CHOISE = (('PR', 'In process'), ('ST', 'Stopped'), ('FN', 'Finished'), ('DL', 'Deleted'),)
class Task(models.Model):
owner = models.ForeignKey(User)
process = models.ForeignKey(Process)
title = models.CharField(max_length=200, default='')
description = models.CharField(max_length=1000, default='')
date_created = models.TimeField(auto_now_add=True, auto_now=False)
date_deadline = models.DateTimeField(default=lambda: (datetime.now() + timedelta(days=7)), auto_now_add=False)
parameters = jsonfield.JSONField()
objects = InheritanceManager()
status = models.CharField(max_length=2, choices=STATUS_CHOISE, default='ST')
here the HumanTask that extends Task
PLATFORMS = (('CC', 'CrowdComputer'), ('MT', 'Amazon Mechancial Turk'),)
class HumanTask(Task):
number_of_instances = models.IntegerField(default=1)
uuid = models.CharField(max_length=36, default='')
page_url = models.URLField(max_length=400, default='', null=True, blank=True)
platform = models.CharField(max_length=2,choices=PLATFORMS, default='CroCo')
validation=models.OneToOneField(ValidationTask)
reward = models.OneToOneField(Reward, null=True, blank=True)
now, how should i create the Form? Should i use ModelForm for both classes?
The point is: there are fields that have to be exclude
for example, TaskForm is:
class TaskForm(ModelForm):
owner = forms.ModelChoiceField(queryset=User.objects.all(),widget=forms.HiddenInput)
process = forms.ModelChoiceField(queryset=Process.objects.all(),widget=forms.HiddenInput)
class Meta:
model = Task
exclude = ('date_deadline', 'date_created','parameters','status','objects')
so what i want for the HumanTaskForm is that the exclude are inherited from the TaskForm
i tried with this
class HumanTaskForm(TaskForm):
class Meta:
model= HumanTask
exclude = 'uuid'
but does not work.
Summing up: is this correct? should i use Inheritance for forms? and, how can i have excluded fields, and others parameters, Inheritance?
If you want to leverage the exclude from TaskForm in HumanTaskForm and extend it, you can inherit the Meta class from TaskForm:
class HumanTaskForm(TaskForm):
class Meta(TaskForm.Meta):
model = HumanTask
exclude = TaskForm.Meta.exclude + ('uuid',)
You need to inherit the parent Meta as well as.
The child class will inherit/copy the parent Meta class. Any attribute explicitly set in the child meta will override the inherited version. To my knowledge there is no way to extend the parent Meta attributes (ie adding to 'exclude').
class AwesomeForm(forms.ModelForm):
class Meta:
model = AwesomeModel
exclude = ('title', )
class BrilliantForm(AwesomeForm)
class Meta(AwesomeForm):
model = BrilliantModel
.
print(AwesomeForm.Meta.model)
> AwesomeModel
print(BrilliantForm.Meta.model)
> BrilliantModel
print(AwesomeForm.Meta.exclude)
> ('title', )
print(BrilliantForm.Meta.exclude)
> ('title', )
You could do something like this:
class BrilliantForm(AwesomeForm)
class Meta(AwesomeForm):
model = BrilliantModel
exclude = AwesomeForm.Meta.exclude + ('uuid', )
.
print(BrilliantForm.Meta.exclude)
> ('title', 'uuid')

Please help me understand Django GenericInlineFormSet

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.