How to check unique bulk_create - django

In Django there is a method get_or_create guaranteeing the uniqueness of the object. But when records for adding a lot more 1000 processing takes a lot of time (as 1000 requests for the creation of objects). I know about bulk_create, but it does not check on the uniqueness of the input attributes of the model. How do I speed up the addition of unique objects to the database? If possible in 1 request.
Example model:
models.py
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
admin.py
# Create only one element (What I want to get)
_, created = Person.objects.get_or_create(first_name='Leon', last_name='Mariano')
_, created = Person.objects.get_or_create(first_name='Leon', last_name='Mariano')
# Create 2 objects with different ID
Person.objects.bulk_create(Person(first_name='Leon', last_name='Mariano'),
Person(first_name='Leon', last_name='Mariano'))

Thanks to Ivan for the help. The solution is:
models.py
from django.db.models import UniqueConstraint
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class Meta:
constraints = [
UniqueConstraint(fields=['first_name', 'last_name'], name='unique person')
]
admin.py
Person.objects.bulk_create((
Person(first_name='fname', last_name='lname'),
Person(first_name='fname', last_name='lname'),
Person(first_name='fname2', last_name='lname2'),
), ignore_conflicts= True)

Related

Prefetching extra fields in a ManyToMany table

I am working with Django on a database that has additional fields on intermediate models. Since it's a big database, I try to optimize the way the data is loaded. But I have a problem with the extra fields of the association table.
Let's take this example from Django's documentation :
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
I would like to retrieve, from each entity of the Group class, all the entities of the Person class and all the fields invite_reason or date_joined.
To retrieve the persons, it goes fast with the QuerySet.prefetch_related attribute that prevents the deluge of database queries that is caused by accessing related objects.
groups = Group.objects.prefetch_related('members')
However, I did not find a solution to retrieve in a constant access time the extra fields invite_reason and date_joined.
I tried prefetching membership_set or a related name in my variable groups but my code doesn't go faster.
# NOT WORKING
groups = Group.objects.prefetch_related('members', 'membership_set')
I also tried using a Prefetch object with a queryset parameter using select_related but it didn't work. Everything I've tried to load all the Membership data into groups at initialization has failed and I end up having a very long runtime retrieving the extra fields from the table.
# TAKES A WHILE BECAUSE NOTHING IS PREFETCHED
for group in groups:
invite_reason_list = group.membership_set.values_list('invite_reason', flat=True)
date_joined_list = group.membership_set.values_list('date_joined', flat=True)
How do I stop the deluge of database queries that is caused by accessing related objects?
When you don't write related_name.all() on prefetching, it does not work as expected. you can get the data like this:
prefetch_membership_set = models.Prefetch('membership_set',
Membership.objects.only(
'date_joined', 'invite_reason'))
groups = Group.objects.prefetch_related(prefetch_membership_set)
for group in groups:
invite_reason_list = []
date_joined_list = []
for membership in group.membership_set.all():
invite_reason_list.append(
membership.invite_reason
)
date_joined_list.append(
membership.date_joined
)

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')
)
]

Put a constraint on an attribute of a related model

I have two models: Account and Transfer.
The model Account has the attribute currency (EUR, USD, JPY, etc.).
The model Transfer has two attributes account_from and account_to.
I want to add a constraint that checks that account_from uses the same currency as account_to.
I was thinking of adding such a constraint on Transfer model:
class Meta:
constraints = [
CheckConstraint(check=Q(account_from__currency=F('account_to__currency')), name='same_currency'),
]
But that doesn't work because of error
django.core.exceptions.FieldError: Joined field references are not permitted in this query
How do I do that ? Without relying on SQL. I know how to do that in SQL but I want to use the ORM. Or is it impossible to do that with Django ORM ?
Here are the two models (simplified to avoid noise):
class Tranfer(AuditedModel):
"""
The action of moving money from one account to another
"""
account_from = models.ForeignKey(Account, related_name="outgoing", on_delete=models.CASCADE)
account_to = models.ForeignKey(Account, related_name="incoming", on_delete=models.CASCADE)
class Account(AuditedModel):
"""
Recipient for money
"""
currency = models.CharField('currency', max_length=3, choices=(('EUR', 'Euro'), ('USD', 'American Dollars'), ('JPY', 'Japan Yen')))
class Meta:
constraints = [
CheckConstraint(check=Q(account_from__currency=F('account_to__currency')), name='same_currency'),
]
Follow the following steps for the solution of your problem.
The constrain section should be in the Transfer Model not in Account model.
Check constrain for any two relational model must be avoided.
The alternative for CheckConstraint() in the Constraint array of Meta section use the function clean for validation rather than constraint.
from django.db import models
from django.core.exceptions import ValidationError
class Account(models.AuditedModel):
"""
Recipient for money
"""
currency = models.CharField('currency', max_length=3, choices=(
('EUR', 'Euro'),
('USD', 'American Dollars'),
('JPY', 'Japan Yen')
))
class Transfer(models.AuditedModel):
"""
The action of moving money from one account to another
"""
account_from = models.ForeignKey(Account, related_name="outgoing", on_delete=models.CASCADE)
account_to = models.ForeignKey(Account, related_name="incoming", on_delete=models.CASCADE)
def clean(self):
if self.account_from.currency != self.account_to.currency:
raise ValidationError("Same currency required for transaction.")
return super().clean()

Create number of fields based from choices

*I'm trying to figure out how to populate fields in my model based on previous field selection.
For example, if FIELD_CHOICES = 3
Create 3x TextField()
class Post(models.Model):
STATUS_CHOICES = (('published','Published'),
('draft','Draft '))
FIELD_CHOICES = (('1','1 Title and body field'),
('2','2 Title and body fields'),
('3','3 Title and body fields'),
('4', '4 Title and body fields'),
('5', '5 Title and body fields'))
author = models.ForeignKey(User,
on_delete=models.CASCADE,
related_name='blog_post')
title = models.CharField(max_length=100)
sub_title = models.TextField(max_length=50,default="")
title_and_body_fields = models.IntegerField(choices=FIELD_CHOICES,
default=1)
**/// create number of title and body Textfields based on what was
/// selected in title_and_body_fields**
created = models.DateField()
publish = models.DateTimeField(default=timezone.now)
slug = models.SlugField(max_length=250,
unique_for_date='created')
status = models.CharField(max_length=250,
choices=STATUS_CHOICES,
default='draft')
object = models.Manager()
postManager = PostManager()
class Meta():
ordering = ('publish',)
def __strd__(self):
return self.title
def get_absolute_url(self):
return reverse('my_blog:post_detail',
args=[self.publish.year,
self.publish.month,
self.publish.day,
self.slug])
In the end i decided to do the following.
I added 5 seperate TextFields for Title and Body and added blank=True
title_5 = models.CharField(max_length=100,null=True,blank=True)
title_5_body = models.TextField(null=True,blank=True)
I'm afraid this is not possible.
I think you misunderstand how Models work. Have a look at the Django docs for models here.
Basically the way Django saves your models, is through an ORM (Object-Relational-Mapping).
This means that the model you write, with the fields contained within it, are transformed into a database query, generating a table.
In the docs for models, you can see this piece of code:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
is transformed to this SQL query:
CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
This means that Django, under the hood, creates database tables for each Model you have in your app.
So how does work in Django?
Well, these SQL statements are generated when you run python manage.py makemigrations. The actual tables are created when you run python manage.py migrate.
This means that you only run these commands once (For every change to your model), and there will be only one table created for your model.
So why can't I add extra TextFields to an object?
This is because for each model there is only one table in the database, and your model would be stored like this:
id | first_name | last_name
----------------------------------
1 | John | Doe
2 | Jane | Doe
... | ... | ...
If you were to add an extra field (for instance phone_number) to John Doe, you'd need to add that field to the entire table.
So in your case, your choice is between no extra body and title fields, or a set amount extra fields for each object.
Ok, what now?
Well, there are a few ways to do this. Your best bet would be to create a ManyToMany relationship to a Model called something like PostBody, which would allow you to create an arbitrary amount of bodies for a set post.
You could modify the save method of you Post model to automatically create a set amount of PostBody objects for that object.
You can read read more about ManyToManyField here.

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)