This is my models to store availability of particular time when a new booking there
class TimeSlot(models.Model):
day = models.ForeignKey(
Day,
on_delete=models.CASCADE,
related_name="time"
)
booking = models.ForeignKey(
Booking,
on_delete=models.CASCADE,
related_name="time"
)
start_hour = models.TimeField()
end_hour = models.TimeField()
class Meta:
unique_together = [('end_hour', 'start_hour',)]
def clean(self):
pass
Currently it's allowing booking even those are considered as duplicate in terms of end_hour and start_hour. I want to prevent the slot, so that no new booking shouln't placed between a range that already booked.
Can anyone know how to do it with the range?
I assume the problem is that start_hour and end_hour that fall within an already existing time range are allowed to be added. Of course the unique_together constraint cannot handle this as it only deals with uniqueness not uniqueness in a range. Instead you can override your models clean method and perform this validation there:
from django.db.models import Q
from django.core.exceptions import ValidationError
class TimeSlot(models.Model):
day = models.ForeignKey(
Day,
on_delete=models.CASCADE,
related_name="time"
)
booking = models.ForeignKey(
Booking,
on_delete=models.CASCADE,
related_name="time"
)
start_hour = models.TimeField()
end_hour = models.TimeField()
class Meta:
unique_together = [('end_hour', 'start_hour',)]
def clean(self):
start_hour_in_range = Q(start_hour__lte=self.start_hour, end_hour__gte=self.start_hour)
end_hour_in_range = Q(start_hour__lte=self.end_hour, end_hour__gte=self.end_hour)
# Queryset that finds all clashing timeslots with the same day
queryset = self._meta.default_manager.filter(start_hour_in_range | end_hour_in_range, day=self.day)
if self.pk:
queryset = queryset.exclude(pk=self.pk) # Exclude this object if it is already saved to the database
if queryset.exists():
raise ValidationError('An existing timeslot clashes with the given one!')
Next if you are using a ModelForm this method would be called automatically or if you are not you can call instance.full_clean() which will call this method and all other cleaning methods on the model (clean_fields and validate_unique).
Related
I have a page that displays a list of interviewers and some important info about each user.
One of the things that I want it to show is the number of users who have been interviewed by that specific interviewer.
I wrote a view like this:
class ManagerUsers(ListView):
model = User
template_name = 'reg/manager-users.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['scientific_interviewers'] = User.objects.filter(role='theory_interviewer').all()
context['interviewed_number'] = len(ScientificInfo.objects.filter(user__role='applicant', is_approved=True, interviewer=?????))
the interviewer field should be equal to that object's user but I don't know what to do exactly.
the output should be something like this:
object 1 : user's name, user's other info, user's interviewed_number
....
these are my models:
USER_ROLE_CHOICES = (('0', 'applicant'),
('1', 'theory_interviewer'),)
class User(AbstractUser):
id = models.AutoField(primary_key=True)
role = models.CharField(max_length=25, null=True, choices=USER_ROLE_CHOICES, default=USER_ROLE_CHOICES[0][0])
username = models.CharField(unique=True, max_length=13)
first_name = models.CharField(max_length=32, null=True, default=None)
last_name = models.CharField(max_length=64, null=True, default=None)
class ScientificInfo(models.Model):
id = models.AutoField(primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user')
interviewer = models.OneToOneField(User, on_delete=models.CASCADE, related_name='interviewer')
is_approved = boolean field
You can override the .get_queryset() method [Django-doc] to return only the interviewers. By using .annotate(…) [Django-doc] you can add an extra attribute to these Users:
from django.db.models import Count
class ManagerUsersView(ListView):
model = User
context_object_name = 'scientific_interviewers'
template_name = 'reg/manager-users.html'
def get_queryset(self):
return super().get_querset().filter(
role='1'
).annotate(
interviewed_number=Count('interviewer', filter=Q(interviewer__user__role='0', interviewer__is_approved=True))
)
The Users that arise from this queryset will have an extra attribute .interviewed_number with the number of approved ScientificInfos where that user was the interviewer.
Note: In Django, class-based views (CBV) often have a …View suffix, to avoid a clash with the model names.
Therefore you might consider renaming the view class to ManagerUsersView, instead of ManagerUsers.
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the User model to the ScientificInfo
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the interviewer relation to interviews.
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 am having issues with the performance of a couple of queries in my Django app... all others are very fast.
I have an Orders model with OrderItems, the query seems to be running much slower than other queries (1-2 seconds, vs. 0.2 seconds). I'm using MySQL backend. In the serializer I do a count to return whether an order has food or drink items, I suspect this is causing the performance hit. Is there a better way to do it?
Here is my models setup for Order and OrderItems
class Order(models.Model):
STATUS = (
('1', 'Placed'),
('2', 'Complete')
)
PAYMENT_STATUS = (
('1', 'Pending'),
('2', 'Paid'),
('3', 'Declined'),
('4', 'Manual')
)
shop= models.ForeignKey(Shop,on_delete=models.DO_NOTHING)
customer = models.ForeignKey(Customer,on_delete=models.DO_NOTHING)
total_price = models.DecimalField(max_digits=6, decimal_places=2,default=0)
created_at = models.DateTimeField(auto_now_add=True, null=True)
time_completed = models.DateTimeField(auto_now_add=True, null=True,blank=True)
time_cancelled = models.DateTimeField(auto_now_add=True, null=True,blank=True)
status = models.CharField(max_length=2, choices=STATUS, default='1',)
payment_method = models.CharField(max_length=2, choices=PAYMENT_METHOD, default='3',)
payment_status = models.CharField(max_length=2, choices=PAYMENT_STATUS, default='1',)
type = models.CharField(max_length=2, choices=TYPE, default='1',)
def __str__(self):
return str(self.id)
class OrderItem(models.Model):
order = models.ForeignKey(Order,on_delete=models.CASCADE)
type = models.CharField(max_length=200,default='DRINK')
drink = models.ForeignKey(
Drink,
blank=True,null=True,on_delete=models.DO_NOTHING
)
food = models.ForeignKey(
Food,
blank=True,
null=True,
on_delete=models.DO_NOTHING
)
quantity = models.IntegerField(blank=True,null=True)
price = models.DecimalField(max_digits=6, decimal_places=2,default=0)
created_at = models.DateTimeField(auto_now_add=True, null=True)
delivered = models.BooleanField(default=False)
def __str__(self):
return str(self.id)
In my rest order serializer, here is the query for get,
queryset = Order.objects.filter(shop=shop,status__in=['1','2'],payment_status__in=['2','4'])
The serializer is below, but this query is quite slow. I assume because I am doing a count() on OrderItems - is there a more efficient way to do this?
class OrderOverviewSerializer(serializers.ModelSerializer):
tabledetails = serializers.SerializerMethodField()
has_food = serializers.SerializerMethodField()
has_drink = serializers.SerializerMethodField()
class Meta:
model = Order
fields = ['id','total_price', 'created_at','has_food','has_drink','type','status','shop','table','customer','shopdetails']
def get_shopdetails(self, instance):
qs = Shop.objects.get(id=instance.shop.id)
serializer = ShopSerializer(instance=qs, many=False)
return serializer.data
def get_has_food(self, obj):
foodCount = OrderItem.objects.filter(order=obj.id,type='FOOD').count()
return foodCount
def get_has_drink(self, obj):
drinkCount = OrderItem.objects.filter(order=obj.id,type='DRINK').count()
return drinkCount
There reason for that is famous N+1 problem that Django ORM is so inept to handle. The solution for it is to use select_related answerd in this question. More on that here.
You should consider db_index=True on the fields you're querying over (Order.status, Order.payment_status, OrderItem.type).
get_shopdetails() isn't used for anything in the serializer? (On a similar note, the getter for tabledetails is missing... are you maybe presenting some code that's not exactly what you're running?)
get_shopdetails() is redundant anyway; you can simply declare shop = ShopSerializer() and DRF will know what to do.
If the get_has_food/get_has_drink fields did prove to be the bottleneck (which they apparently didn't), you could use a Django aggregate to count the rows during the query for orders.
Speaking of, your serializer is accessing several foreign keys, which will all cause N+1 queries; you can add .select_related('shop', 'customer', 'table') (or .prefetch_related() the same) at the very least to have those get loaded in one fell swoop.
Beyond this -- profile your code! The easiest way to do that is to copy the skeleton from manage.py and add some code to simulate your query, e.g. (this is dry-coded):
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_settings")
import django
django.setup()
from django.test import TestClient
c = TestClient()
for x in range(15):
c.get("/api/order/") # TODO: use correct URL
and run your script with
python -m cProfile my_test_script.py
You'll see which functions end up taking the most time.
i am trying to make a key verification app that when accepted it would create the same app on the user's profile, so i have done everything but i have struggled making the expiration date part, i want the expired boolean become true when the date is expired, but i have no idea on how to implement it
#models.py
class ProductKey(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE,
unique=False)
key = models.CharField(max_length=14)
valid_from = models.DateTimeField(default=timezone.now)
valid_to = models.DateTimeField()
expired = models.BooleanField(default=False)
Please do not add a database field for this. You will introduce data duplication: if you later set the valid_to, you will have to update expred as well, so the logic would introduce extra challenges.
You can annotate your ProductKey model, such that objects that arise from this have an attribute expired:
from django.db.models import BooleanField, ExpressionWrapper, Q
from django.db.models.functions import Now
ProductKey.objects.annotate(
expired=ExpressionWrapper(Q(valid_to__lt=Now()), output_field=BooleanField())
)
You can then filter on that property. For example you can retrieve the ProductKeys that are expired with:
ProductKey.objects.annotate(
expired=ExpressionWrapper(Q(valid_to__lt=Now()), output_field=BooleanField())
).filter(expired=True)
If you need this often, you can annotate this in the manager, like:
class ExpiredManager(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(
expired=ExpressionWrapper(Q(valid_to__lt=Now()), output_field=BooleanField())
)
class ProductKey(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE,
unique=False)
key = models.CharField(max_length=14)
valid_from = models.DateTimeField(default=timezone.now)
valid_to = models.DateTimeField()
objects = ExpiredManager()
You could use a property for this use case and calculate that on the fly.
#property
def expired(self):
# calculate here if its still valid
I have a Region infrastructure modeled as follows: Each region has a ManyToMany of countries, and optionally states (if it's a region within the US)
from django.contrib.auth.models import User
from django.contrib.localflavor.us.models import USStateField
from django.db import models
from django_countries import CountryField
class CountryManager(models.Manager):
def get_by_natural_key(self, country):
return self.get(country=country)
class Country(models.Model):
country = CountryField(unique=True)
objects = CountryManager()
class Meta:
ordering = ('country',)
def __unicode__(self):
return unicode(self.country.name)
def natural_key(self):
return (self.country.code,)
class StateManager(models.Manager):
def get_by_natural_key(self, state):
return self.get(state=state)
class State(models.Model):
state = USStateField(unique=True)
objects = StateManager()
class Meta:
ordering = ('state',)
def __unicode__(self):
return self.get_state_display()
def natural_key(self):
return (self.state,)
class Region(models.Model):
name = models.CharField(max_length=255, unique=True)
coordinator = models.ForeignKey(User, null=True, blank=True)
is_us = models.BooleanField('Is a US region')
countries = models.ManyToManyField(Country)
states = models.ManyToManyField(State, blank=True)
class Meta:
ordering = ('name',)
def __unicode__(self):
return self.name
Each user has a profile defined (partially) as follows:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='user_profile')
city = models.CharField(max_length=255)
country = CountryField()
state = USStateField(_(u'US only (determines user's region)'), blank=True, null=True)
I'm trying to filter a bunch of user objects by region. So I have
region = Region.objects.filter(id=self.request.GET['filter_region'])
if len(region) == 0:
raise Exception("Region not found for filter")
if len(region) > 1:
raise Exception("Multiple regions found for filter?")
region = region[0]
queryset = queryset.filter(user_profile__country__in=region.countries.all)
Sadly though, this returns an empty queryset. I suspect it has to do with the fact that the "Country" model has a "country" field within it (terrible ambiguous naming, I know, not my code originally), and I'm only filtering by "Country" models, not the "country" fields within them. (Does that make sense?)
How can I filter by a subfield of the ManyToMany field?
First, why are you using .filter() if you want just a single item do:
region = Region.objects.get(id=self.request.GET['filter_region'])
That will raise an ObjectDoesNotExist exception if the object doesn't exist, but you're raising an exception if the queryset is empty anyways. If you need to catch that exception you can either use a try...except block or get_object_or_404 if you're in a view.
Second, don't use self.request.GET['filter_region'] directly. If the key isn't set you'll raise an IndexError. Use, instead:
self.request.GET.get('filter_region')
Now, as to your actual problem: UserProfile.country is a CountryField which is just a specialized CharField. Whereas Region.countries is a M2M with the model Country. The two are not comparable, which is why your queryset is coming back empty.
Make UserProfile.country a foreign key to Country and you're in business.