How to add these constraints to Django models? - django

Help me please. How to make constraints in the database?
I am using sqlite, django 3.2, python 3.7
I have three models: Refbook, Versions of refbooks, Elements of refbook
from django.db import models
class Refbook(models.Model):
code = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=300)
description = models.TextField()
class VersionRefbook(models.Model):
refbook_id = models.ForeignKey('Refbook',
on_delete=models.CASCADE,
related_name='versions')
version = models.CharField(max_length=50)
date = models.DateField()
class Meta:
unique_together = ('refbook_id', 'version')
class Element(models.Model):
version_id = models.ManyToManyField('VersionRefbook')
code = models.CharField(max_length=100)
value = models.CharField(max_length=300)
The following constraints must be added to these models:
There cannot be more than one Refbook with the same value in the "code" field.
There cannot be more than one Version with the same set of values "refbook id" and "version"
One Refbook cannot have more than one Version with the same date
In one Version of refbook, there cannot be more than one Element of refbook with the same value in the "code" field
OK. I solved the first point by adding the «unique=true» parameter to the «code» field in the Refbook class.
It seems that I solved the seconf point by adding the «unique_together = ('refbook_id', 'version')» parameter to the Meta class.
Third point. I think need to override the save method. But how? And is it right?
Fourth point. I wanted to add a parameter « unique_together = ('version_id', 'code')» to the Meta class of the Element class, but there is a connection ManyToMany there. It gives an error message.
Have I added the first and second constraints correctly? And how to solve the third and fourth?

Seems you have added the first two constraints correctly.
3. One Refbook cannot have more than one Version with the same date
Just add another unique constraint for version and date.
class VersionRefbook(models.Model):
refbook_id = models.ForeignKey(
'Refbook',
on_delete=models.CASCADE,
related_name='versions'
)
version = models.CharField(max_length=50)
date = models.DateField()
class Meta:
unique_together = ('refbook_id', 'version')
unique_together = ('version', 'date')
4. In one Version of refbook, there cannot be more than one Element of refbook with the same value in the "code" field
One solution could be to use intermediate model:
class Element(models.Model):
version_id = models.ManyToManyField(
'VersionRefbook',
through='ElementVersionRefbook'
)
value = models.CharField(max_length=300)
class ElementVersionRefbook(models.Model):
version = models.ForeignKey(VersionRefbook, on_delete=models.CASCADE)
element = models.ForeignKey(Element, on_delete=models.CASCADE)
code = models.CharField(max_length=100)
class Meta:
unique_together = ('version', 'code')
You can create instances as below:
elem = Element.objects.create(value='some value')
refbook = Refbook.objects.create(
code='some code',
name='some name',
description='some desc'
)
version = VersionRefbook.objects.create(
refbook_id=refbook,
version='1.0',
date='2023-01-01'
)
elem_version_refbook = ElementVersionRefbook.objects.create(
version=version,
element=elem,
code='123'
)
elem_version_refbook = ElementVersionRefbook.objects.create(
version=version,
element=elem,
code='345'
)
If you try to add ElementRefbook entry with same version and code as below, it will raise IntegrityError
elem_version_refbook = ElementVersionRefbook.objects.create(
version=version,
element=elem,
code='123'
)

Related

Django-import-export doesn't skip unchanged when skip_unchanged==True

I'm building an app using Django, and I want to import data from an Excel file using django-import-export.
When importing data I want to skip unchanged rows, for this, I'm using skip_unchanged = True in the resource class (like below) but I get unexpected behavior. In my model, I have an attribute updated_at which is a DateTimeField with auto_now=True attribute, it takes a new value each time I upload the Excel file even if the values of rows have not changed in the file.
Below are portions of my code.
models.py
class HREmployee(models.Model):
code = models.IntegerField()
name_en = models.CharField(max_length=55)
status = models.CharField(max_length=75)
termination_date = models.DateField(null=True)
hiring_date = models.DateField()
birth_date = models.DateField()
# other fields to be imported from the file ...
# fields that I want to use for some purposes (not imported from the file)
comment = models.TextField()
updated_at = models.DateTimeField(auto_now=True)
resources.py
class HREmployeeResource(ModelResource):
code = Field(attribute='code', column_name='Employee Code')
name_en = Field(attribute='name_en', column_name='Employee Name - English')
status = Field(attribute='status', column_name='Employee Status')
termination_date = Field(attribute='termination_date', column_name='Termination Date')
hiring_date = Field(attribute='hiring_date', column_name='Hiring Date')
birth_date = Field(attribute='birth_date', column_name='Birth Date')
# other fields to be imported ...
class Meta:
model = HREmployee
import_id_fields = ('code', )
skip_unchanged = True
How can I fix this unexpected behavior?
Edit
After few tries, I've found that columns with date values are causing this problem.
In the Excel file, I have three columns that have date values like in the picture below, when I comment the corresponding attributes in the resource class and do the import, I get the expected behavior (if no changes in the file the import_type equals skip and no changes are made in the DB).
I've edited the code of the model and resource classes (please check above).
This should be easy to fix, simply use the fields parameter to define only the fields you wish to import (docs):
class Meta:
...
fields = ('code', 'name',)
If skip_unchanged is True, then only these fields will be compared for changes, and the instance will be updated if any one of them has changed, otherwise it will be skipped.
The field name has to be the model attribute name, not the name of the column in the import.
Sorry for the revival of that post, but I was stucked on the same issue, and I found only this post exaclty related to, so i post my answer.
In my model I've defined dateField and not DateTimeField.
But it import as DateTimeField, so the comparison failed.
To compare carrots with carrots, I defined a field class to convert values if needed :
import datetime
class DateField(Field):
def get_value(self, obj):
val=super().get_value(obj)
if isinstance(val, datetime.datetime):
return val.date()
return val
and then in my resource
class HREmployeeResource(ModelResource):
hiring_date = DateField(attribute='hiring_date', column_name='Hiring Date')
birth_date = DateField(attribute='birth_date', column_name='Birth Date')
# ....

Create object in model having foreigkey relation

I want to create an entry in this Something model in python manage.py shell using this
Someting.objects.create(discussion_title="General", user_username="admin", content="Hello")
models example
class Discussion(models.Model):
title = models.CharField(max_length=255, unique=True, blank=False,)
users = models.ManyToManyField(User, blank=True, )
class Something(models.Model):
user = models.ForeignKey(User,
on_delete=models.CASCADE)
discussion = models.ForeignKey(Discussion, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
content = models.TextField(unique=False, blank=False)
I am getting this error
TypeError: Something() got an unexpected keyword argument 'discussion_title'
First, you have to use double under bar __ to use django's model relation expression.
Someting.objects.get(discussion__title="General", user__username="admin", content="Hello")
Second, you can't use double under bar relation expression when create an object.
if you want to create an object in relation, you have to create in step by step. follow #Nicolas Appriou 's answer
Your Something model does not have a discussion_title field. You need to create a Discussion instance for this.
This model does not have a user_username model either.
discussion = Discussion.objects.create(title="Foobar")
discussion.users.add(User.objects.create(username="Ham")
Something.objects.create(
discussion=discussion,
)

How to define two Django fields unique in certain conditions

I want to make Django Model fields unique with two fields(values) in some conditions.
there's two fields: 'team', 'type'. And I want to make team manager unique
For Example:
team=1, type='manager'
team=1, type='manager'
-> Not available
team=1, type='manager'
team=1, type='member'
team=1, type='member'
team=2, type='manager'
-> Available
I think unique_together('team', 'type') won't work properly with this situation.
How can I make this with Django Model?
Here's my model below:
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
I think, You need to use UniqueConstraint for your application which work perfect in kind of situation.
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
constraints = [
models.UniqueConstraint(fields=['team', 'type'], name='unique_team')
]
you can also refer this link for more understanding. and let me know if following solution will work.
Given that there is a deprecation warning in the documentation (based on 3.2 docs) for unique_together, I think it's worth showing that this can be done using UniqueConstraint. I believe that the key missing ingredient from the previous answer is the use of UniqueConstraint.condition, like so:
from django.db import models
from django.db.models import Q, UniqueConstraint
class myModel(models.Model):
team = models.ForeignKey('Team', on_delete=models.CASCADE)
type = models.CharField(max_length=10, default='member')
class Meta:
db_table = 'my_models'
constraints = [
UniqueConstraint(
fields=['team', 'type'],
name='unique_team',
condition=Q(type='manager')
)
]

Django (Model)Form Field: Manytomany with key value pair

I have a situation where I need to do something similar to rendering a formset within a formset. But I'd rather focus on the problem before jumping to a solution.
In English first:
I'm creating a shipment from a warehouse.
Each shipment can contain multiple lines (unique combinations of product_type and package_type) with an item_count
However for each line there could be multiple "Packages" - a package_type of a product_type that has an item_count. Think of this as a batch.
The customer is only interested in seeing one line for each product_type/package_type
But we need to pull out the stock and correctly attribute the particular units from each batch to allow stock control, recall control etc to function. Therefore the dispatch staff IS interested in exactly which Packages are shipped.
Add to this the sales staff enter a SalesOrder that only specifies the product_type/package_type. They aren't interested in the Packages either. (Think putting in a forward order for next month - who knows what will be in stock then?).
Now the models (simplified for clarity):
class Package(models.Model):
create_date = models.DateField()
quantity = models.FloatField()
package_type = models.ForeignKey(PackageType, on_delete=models.PROTECT)
product_type = models.ForeignKey(ProductType, on_delete=models.PROTECT)
class CheckOut(models.Model):
package = models.ForeignKey(Package, on_delete=models.PROTECT)
create_date = models.DateField()
quantity = models.FloatField()
class Shipment(models.Model):
sales_order = models.ForeignKey(SalesOrder, null=True, blank=True)
ship_date = models.DateField(default=date.today,
verbose_name='Ship Date')
class ShipmentLine(models.Model):
shipment = models.ForeignKey(Shipment, null=True, blank=True)
sales_order_line = models.ForeignKey(SalesOrderLine, null=True, blank=True)
quantity = models.FloatField(verbose_name='Quantity Shipped')
checkout = models.ManytoManyField(CheckOut)
I currently have it working well with the constraint of a 1:M relationship of CheckOut:ShipmentLine. However when changing this to a M:M, things get knarly form-wise.
In the 1:M version the Shipment form (plus formset for the ShipmentLines) looks like this:
class CreateShipmentForm(forms.ModelForm):
class Meta:
model = om.Shipment
contact = forms.ModelChoiceField(
queryset=om.Contact.objects.filter(is_customer=True, active=True),
label='Customer')
customer_ref = forms.CharField(required=False, label='Customer Reference')
sales_order = forms.ModelChoiceField(queryset=om.SalesOrder.objects.all(),
required=False, widget=forms.HiddenInput())
number = forms.CharField(label='Shipment Number', required=False,
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
class CreateShipmentLineForm(forms.ModelForm):
class Meta:
model = om.ShipmentLine
widgets = {
'checkout': forms.HiddenInput()
}
fields = ('package', 'quantity', 'id',
'sales_order_line', 'checkout')
id = forms.IntegerField(widget=forms.HiddenInput())
sales_order_line = forms.ModelChoiceField(
widget=forms.HiddenInput(), required=False,
queryset=om.SalesOrderLine.objects.all())
package = forms.ModelChoiceField(required=True, queryset=None) # queryset populated in __init__, removed for brevity
So for the 1:M, I could select a package, set the quantity and done.
For M:M, I will need to select product_type, package_type, and then 1 or more packages, AND for each package a quantity. (I'll be using JS in the form to filter these)
In my mind's eye I have a few possibilities:
create a (child) formset for the Packages and quantities and include in each line of the (parent) formset
create some sort of multi-field, multi-value matrix custom form field and use that
construct a modal dialog where the M:M stuff happens and somehow save the result to the form where validation, saving happens.
I hope I have explained it correctly and clearly enough. It's the most complex application of Django forms I've encountered and I'm not sure what the limitations/pros/cons of each of my options is.
Has anyone encountered this situation and have a solution? Or any words to the wise?
My thanks in advance,
Nathan
I have a similar situation, I am doing something like your second and third options:
I have overridden __init__() and, after calling super, I have a loop that adds a value selector for every field (of course you could use a single custom element here)
Then override save() and after calling super I process the extra field adding all the values.

How to make a query on related_name field?

I have to models connected by a ForeignKey
class User(AbstractUser):
...
and
class PrivateMessage(models.Model):
user_from = models.ForeignKey(
User,
verbose_name=u'From',
related_name='sent_messages',
)
user_to = models.ForeignKey(
User,
verbose_name=u'To',
related_name='received_messages',
)
Is there any way to get all the addresses for a particular user. For example, if
u = User.objects.get(id=1)
messages = PrivateMessage.objects.filter(user_from=u)
for m in messages:
users.add(m.user_to)
How to obtain a list of users that appear in user_to for these messages using only Django ORM methods?
I think a better idea would be to define ManyToManyField on the User model:
class User(AbstractUser):
#...
receivers = models.ManyToManyField('self', through='Message',
symmetrical=False, related_name="senders")
class Message(models.Model):
user_from = models.ForeignKey(MyUser, related_name='messages_from')
user_to = models.ForeignKey(MyUser, related_name='messages_to')
message = models.CharField(max_length=100, default="")
#...
Then to retrieve users list on the other end you simply do:
User.objects.get(id=1).receivers.all() # who I sent the message to
User.objects.get(id=1).senders.all() # who sent me a message
This way you have a nice clear API.
Finally, I ended up writing three queries:
users_from = set(PrivateMessage.objects.filter(
user_to=self.request.user,
).values_list(
'user_from__pk',
flat=True,
))
users_to = set(PrivateMessage.objects.filter(
user_from=self.request.user,
).values_list(
'user_to__pk',
flat=True,
))
interlocutors = User.objects.filter(pk__in=users_from.union(users_to))
I saw this docs
Maybe you can try:
u = User.objects.get(id=1)
users = User.objects.filter(received_messages__user_from=u).distinct()
related_name field makes our queries especially the ones using foreign key (on to many relation) easier, shorter and cleaner.
Let say we have 2 models classes Library and Book.
class Library(Models.model):
name = models.CharField(max_length = 100)
`class Book(Models.model):
title = models.CharField(max_length = 100)
library = models.ForeignKey(Library,
on_delete = models.CASCADE,
related_name = 'books')`
Here we have a one to many relation from Library to Book using foriegn key.
And in my django shell. I can create a new Library and a book related to that library in the following manner.
`from <app_name>.models import *`
`library = Library.objects.create(name = 'Big Library')`
`Book.objects.create(title = 'Awesome book', library = library`
Now I can query the book of the library using related name of model Book class:
`library.books.all()`
rather than using the starting the query from Book model as:
Book.objects.filter(library = library)