Is it possible to define a foreign key or OneToOne relation in django model with only subset of data?
For example :
I have 2 models.
#with_author
class Product(models.Model):
GTIN = models.CharField(max_length=30)
material = models.ForeignKey(Material, on_delete=models.PROTECT)
UOM = models.OneToOneField(MaterialUOM)
defaultPrice = MoneyField(max_digits=10, decimal_places=2, default_currency='USD')
and
#with_author
class UOM(models.Model):
uomname = models.CharField(max_length=30)
material = models.ForeignKey(Material, on_delete=models.PROTECT)
so I want in my Product model only to allow UOM values that have same material value as in product.
Is it possible on model level or any other place and not to display non relevant values in the dropdown?
You can enforce this constraint by adding some validation to the model's clean() method. Something like:
from django.core.exceptions import ValidationError
class Product(models.Model):
GTIN = models.CharField(max_length=30)
material = models.ForeignKey(Material, on_delete=models.PROTECT)
UOM = models.OneToOneField(MaterialUOM)
defaultPrice = MoneyField(max_digits=10, decimal_places=2, default_currency='USD')
def clean(self):
if not self.material == self.UOM.material:
# This will cause the model not to be saved and report an error
raise ValidationError('Material does not match UOM material')
If you are using a ModelForm to handle edits to your models, then clean() will be called automatically as part of the form validation. If you are modifying models directly in your code, then you need to call it yourself before saving the model. The documentation explains this in detail.
If you want to be doubly sure, you can also override the save() method:
def save(self, *args, **kwargs):
if not self.material == self.UOM.material:
return # Model is not saved
super(Product, self).save(*args, **kwargs)
This will not report any errors - it will just not save the model. Hence you should also use the clean() method above.
Related
I am using Django 3.2
I have a model like this:
class BannedUser(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="ban_info")
reason = models.PositiveSmallIntegerField(choices=BANN_REASON_CHOICES)
banned_at = models.DateTimeField(auto_now_add=True)
expiry_date = models.DateField(null=True, blank=True, help_text=_('Date on which ban expires'))
I want to create a form that instead of asking user to select a date, simply asks the user to select the Ban duration. The form will then calculate the expiration_date in the clean() method.
BAN_DURATION_3_DAYS=3
# ...
BAN_DURATION_CHOICES = (
(BAN_DURATION_3_DAYS, _('3 Days')),
# ...
)
class BannedUserForm(forms.ModelForm):
class Meta:
model = BannedUser
fields = ['reason', 'ban_till']
The form field ban_till is a PositiveInteger that maps to the number of days. The intention is then to calculate the expiry_date from today by offsetting the integer amount.
I suppose one way would be to:
create a dynamic field ban_till
add field expiry_date to the form field list (but somehow prevent it from being rendered)
in the form's clean() method calculate the expiry_date and update that field
How to create a form to display field that does not exist in Model?
My solution:
class BannedUserForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.fields['ban_till'] = forms.IntegerField(widget=forms.ChoiceField())
super().__init__(*args, **kwargs)
class Meta:
model = BannedUser
fields = ['reason']
Is this the correct way to do this - or are there any gotchas I need to be aware of?
I have a model called Company.
In a second model which is Branch, I use Company as a foreign key.
class Branch(models.Model):
company = models.ForeignKey(Company, on_delete=models.CASCADE)
Now in some other model, I want to set a property(name) unique together with the Company but I use the branch as a foreign key.
class ABC(models.Model):
name = models.CharField()
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
class Meta:
unique_together = (
('branch__company', 'name'),
)
Can I do something like the above? It gives me an error that the field is nonexistent. Or can I use both company and branch in my model as foreign key?
class ABC(models.Model):
name = models.CharField()
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
class Meta:
unique_together = (
('company', 'name'),
)
I want to attach ABC object with a branch but if once added it should be unique to that company (other branches of that company can not have the same name).
Read about the circular error and was thinking of the same here.
Unique together will be depreciated in the future but I'm not thinking about this right now.
Any advice?
I suggest you to perform validation in the clean method (without a database constraint):
from django.core.exceptions import ValidationError
class ABC(models.Model):
name = models.CharField()
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
def clean(self):
super().clean()
if ABC.objects.filter(name=self.name, branch__company=self.branch.company).exists():
raise ValidationError('Error message')
def save(self, *args, **kwargs):
# Forces the clean method to be called
self.full_clean()
super().save(*args, **kwargs)
I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples
I have two models like
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)
Now for an object of Article lets say
a=Article.objects.filter(id=1)
a=a[0]
I try to change the headline and the email of the author who has written this article so I do
a.heagline = "Saving foreign key does not work"
a.reporter.email = "changed#email.com"
a.save()
This saves the Article object but does not modify the Reporter.
I explicitly have to do
a.reporter.save()
to see the changes to the reporter object. As this is a Many to One relation it should also modify the Foreign key on saving
How can I save the parent Model too by just calling the childs save method
You could override the save method or just create a custom method.
class Article(models.Model):
...
# Overriding save
def save(self, *args, **kwargs):
self.reporter.save()
super(Article, self).save(*args, **kwargs)
# Creating a custom method
def save_related(self):
self.reporter.save()
self.save()
I suggest you create a custom method because it doesn't introduce unexpected behavior in save()
I'm struck on saving m2m relation.
models.py
class BasicTag(models.Model):
name = models.CharField(max_length=150, verbose_name="Tag Name")
image_class = models.CharField(max_length=30, blank=True)
color = models.CharField(max_length=10, blank=True)
def __unicode__(self):
return self.name
class ExtendedTag(models.Model):
parent = models.ManyToManyField(BasicTag, blank=True,
related_name="parent")
category = models.ManyToManyField(BasicTag, blank=True,
related_name="category")
def __unicode__(self):
return self._id
class CombineTag(BasicTag, ExtendedTag):
"""
"""
forms.py
class CombineTagForm(forms.ModelForm):
class Meta:
model = CombineTag
Now when I initialize form as
>form = CombineTagForm({"name":"shyam"})#this don't have any parent i.e. root tag
>form.is_valid()
True
>instance = form.save(commit = False)
>instance.save()
>form.save() #return errors
#error
ProgrammingError: column tag_extendedtag_parent.basictag_id does not exist
LINE 1: DELETE FROM "tag_extendedtag_parent" WHERE "tag_extendedtag_...
>form.save_m2m() #return errors ... struck here
So How should I need to save m2m field modelform. I had follow official doc which say that
If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn’t possible to save many-to-many data for an instance until the instance exists in the database.
But I couldn't able to figure out what I am missing here.