How to get objects from db under multiple constraints? - python

I'm trying to figure out how to get particular set of objects from database in Django efficiently.
I can do this using nested loops but I think that it's not a best idea.
I have models Language and UserProfile which has a property method verified_languages which returns a set of Language objects for this UserProfile.
What I want exactly is to create a static method of Language model called get_to_languages(language) which returns a set of all languages of all UserProfiles if these UserProfiles has language attribute in verified_languages property method.
So if there were there these Users -
1st. user:
name = 'Peter'
userprofile.verified_languages = ['english','german','arabic','french']
2nd. user:
name = 'Evgen'
userprofile.verified_languages = ['german','arabic','spanish']
3rd. user:
name = 'Anton'
userprofile.verified_languages = ['russian','arabic','italian']
And I call method get_to_languages(Languages.objects.get(name='german'))
It would return a set of Evgen's and Peter's languages because they know german.
Is it possible to do this using filter?
My old solution:
#staticmethod
def get_languages_to(language):
userprofiles = UserProfile.objects.all()
result = set()
for up in userprofiles:
if language in up.languages_verified:
result.update(up.languages_verified)
result.remove(language)
return list(result)
USERPROFILE:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='userprofile', help_text=_('Related user'))
date_of_birth = models.DateField(null=True, blank=True, help_text=_('Date of birth'))
telephone = models.CharField(max_length=40, null=True, blank=True, help_text=_('Your telephone number'))
IBAN = models.CharField(max_length=40, null=True, blank=True, help_text=_('Bank account unique number'))
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
MARITAL_STATUS_CHOICES = (
('single', 'Single'),
('married', 'Married'),
('separated', 'Separated'),
('divorced', 'Divorced'),
('widowed', 'Widowed'),
)
marital_status = models.CharField(max_length=40, choices=MARITAL_STATUS_CHOICES, null=True, blank=True)
HOW_DO_YOU_KNOW_ABOUT_US_CHOICES = (
('coincidence', u'It was coincidence'),
('relative_or_friends', 'From my relatives or friends'),
)
how_do_you_know_about_us = models.CharField(max_length=40, choices=HOW_DO_YOU_KNOW_ABOUT_US_CHOICES, null=True,
blank=True)
is_translator = models.BooleanField(default=False)
language_levels = models.ManyToManyField('LanguageLevel', blank=True, related_name='translators')
rating = models.IntegerField(default=0)
number_of_ratings = models.BigIntegerField(default=0)
#property
def languages(self):
"""
Returns: all languages of current user include not-verified ones
"""
return [x.language for x in self.language_levels.all()]
#property
def languages_verified(self):
"""
Returns: verified languages of current user
"""
return [x.language for x in self.language_levels.exclude(level__name='unknown')]
def passive_skill(self, language):
"""
True if user has at least passive skill (lowest level) of the language
Args:
language: Language object
Returns: Bool
"""
if language in self.languages_verified:
return True
return False

Well, I think it could be done this way: first, get all LanguageLevel that contains current language and their level_name is not unknown, then get all userprofiles from the result LanguageLevel. In the end, get all languages linked to these userprofiles(apparently it's untested, but please try it out):
language_levels = LanguageLevel.objects.filter(language=language) \
.exclude(level__name='unknown')
userprofiles = UserProfile.objects.filter(language_levels__in=language_levels)
all_languages = profiles.values_list('language_levels__language',
flat=True).distinct()
Note that the __in is very inefficient but that's if you don't have huge amount of records it should be OK. Just pay attention to the performance and see if that goes well.

Related

Django: how to use .filter( ) method in django?

I am trying to display quiz only for users that are registered in a particular course, i.e if a user is registered in a Frontend Crash Course i want them to see only the quiz related to that course they are registered in, and not all the quiz from the db.
i have a model UserCourse where i am storing all the courses a user have enrolled in, when i try filtering by that models while user_course is get like this below
user_course = UserCourse.objects.get(user=request.user)
quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
i get this error get() returned more than one UserCourse -- it returned 3! Now i have changed .get() to .filter() like this
user_course = UserCourse.objects.filter(user=request.user)
quizzes = Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
i then get this error The QuerySet value for an exact lookup must be limited to one result using slicing.
What is the right way to write this query.
models.py
class UserCourse(models.Model):
user = models.ForeignKey(User , null = False , on_delete=models.CASCADE)
course = models.ForeignKey(Course , null = False , on_delete=models.CASCADE, related_name="usercourse")
class Quiz(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="quizzes")
title = models.CharField(max_length=255)
course = models.ForeignKey(Course, on_delete=models.SET_NULL, null=True, related_name="quizzes")
date = models.DateTimeField(auto_now_add=True)
slug = models.SlugField(unique=True)
user_course = models.ForeignKey(UserCourse, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.title
The Problem in the Second Line
user_course = UserCourse.objects.filter(user=request.user)
quizzes=Quiz.objects.filter(course__usercourse=user_course).annotate(questions_count=Count('questions'))
remember that when You are using filter you get QuerySet not one object
if you want to return the quizes those related to user_course_queryset you can use __in filter
print(user_course) # print it to understand more
quizzes=Quiz.objects.filter(course__usercourse__in=user_course)
this will Return every Quiz Related to the QuerySet objects

Django ForeignKey accept two models

I'm working on this big project with Django and I have to update the database. I have to add another table which will replace another later.
So I want to add in a model the possibility to have a field where I can have either the old model OR the new one.
Here is the code of the old model:
class Harvests(models.Model):
ident_culture = models.IntegerField(primary_key=True)
intitule_culture = models.CharField(max_length=50, blank=True)
nom_fertiweb = models.CharField(max_length=50, blank=True, null = True)
affichage_quintaux_tonne = models.CharField(max_length=1,
choices=RENDEMENT_CHOICES, default = 'T')
type_culture = models.ForeignKey("TypeCulture", null=True)
slug = models.SlugField(null=True, blank=True)
image = models.ImageField(upload_to = 'images_doc_culture/',
null=True, blank = True)
affichage = models.BooleanField(default = True)
class Meta:
verbose_name = "Liste - Culture"
verbose_name_plural = "Liste - Cultures"
ordering = ['intitule_culture']
def __str__(self):
return self.intitule_culture
def label(self):
return self.intitule_culture or ''
#classmethod
def get_choices(cls):
choices = [('', corp.EMPTY_CHOICE_LBL)]
c_category_lbl, c_category = '', []
for item in cls.objects.all():
choices.append((item.pk, item.intitule_culture))
return choices
And there is the code od the new one I created:
class Crops(models.Model):
intitule_culture = models.CharField(max_length=75, blank=True)
affichage_quintaux_tonne = models.CharField(max_length=2,
choices=RENDEMENT_CHOICES, default = 'T')
type_culture = models.ForeignKey("TypeCulture", null=True)
ident_culture = models.IntegerField(primary_key=True)
affichage = models.BooleanField(default = True)
id_marle = models.IntegerField(null=True)
class Meta:
verbose_name = "Liste - Culture 2019"
verbose_name_plural = "Liste - Cultures 2019"
ordering = ['intitule_culture']
def __str__(self):
return self.intitule_culture
def label(self):
return self.intitule_culture or ''
#classmethod
def get_choices(cls):
choices = [('', corp.EMPTY_CHOICE_LBL)]
c_category_lbl, c_category = '', []
for item in cls.objects.all():
choices.append((item.pk, item.intitule_culture))
return choices
I want to accept both models in the field culture in this model:
class CompanyHarvest(models.Model):
company = models.ForeignKey('corp.Company', verbose_name='Exploitation',
related_name ='cultures')
culture = models.ForeignKey(Harvests, verbose_name ='Culture')
precision = models.CharField(max_length=255, blank=True)
saison_culture = models.CharField(max_length=1, choices=SAISON_CHOICES,
default = 'P')
class Meta:
verbose_name = "Expl. - Culture"
verbose_name_plural = "Expl. - Cultures"
unique_together = ('company', 'culture', 'precision', 'saison_culture')
def __str__(self):
return str(self.culture) + ' ' + self.precision + \
' ' + str(self.get_saison_culture_display() )
#property
def slug(self):
return "_".join([slugify(str(self.culture or '')),
slugify(str(self.precision or ''))]
)
I'm new to Django, can anyone help me with this please ? (^-^)
This is not possible - at least not this way. And this is not a Django limitation but a SQL one, a foreign key cannot reference either one table or another.
A possible and simple obvious solution here would be to have two foreign keys in CompanyHarvest - one for each of the old and new model -, each with blank=True et default=None, but it can quickly make a mess of all the client code (all code using CompanyHarvest).
Much better solutions would be to either only keep the existing model (adding any new field/feature to it and eventually hiding obsolete ones) or migrate all old model records to the new model (this can be combined with the naive "two foreign keys" solution so you can keep the old table and records as archives if necessary).
Also - totally unrelated but -, this:
#classmethod
def get_choices(cls):
choices = [('', corp.EMPTY_CHOICE_LBL)]
c_category_lbl, c_category = '', []
for item in cls.objects.all():
choices.append((item.pk, item.intitule_culture))
return choices
1/ should be defined on the manager (cf https://docs.djangoproject.com/en/2.1/topics/db/managers/#adding-extra-manager-methods)
2/ should be written using .values() queryset (which will save on both the db query and building full-blown instances for no good reason):
for item in cls.objects.values("pk", "intitule_culture"):
choices.append(item)
3/ and could very possibly (i'd have to see how it's used) replaced by a ModelChoiceField in the calling code.
Oh and yes: if you allow blanks for text fields, you very probably want to force the empty string as default so you don't two possible (and incompatible) cases (sql NULL and the empty string) when no value is given.

Django filter only on aggregate/annotate

I'm trying to construct a fairly complicated Django query and I'm not making much progress. I was hoping some wizard here could help me out?
I have the following models:
class Person(models.Model):
MALE = "M"
FEMALE = "F"
OTHER = "O"
UNKNOWN = "U"
GENDER_CHOICES = (
(MALE, "Male"),
(FEMALE, "Female"),
(UNKNOWN, "Other"),
)
firstName = models.CharField(max_length=200, null=True, db_column="firstname")
lastName = models.CharField(max_length=200, null=True, db_column="lastname")
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, default=UNKNOWN, null=True)
dateOfBirth = models.DateField(null=True, db_column="dateofbirth")
dateInService = models.DateField(null=True, db_column="dateinservice")
photo = models.ImageField(upload_to='person_photos', null=True)
class SuccessionTerm(models.Model):
originalName = models.CharField(max_length=200, null=True, db_column="originalname")
description = models.CharField(max_length=200, blank=True, null=True)
score = models.IntegerField()
class Succession(model.Model):
position = models.ForeignKey(Position, to_field='positionId', db_column="position_id")
employee = models.ForeignKey(Employee, to_field='employeeId', db_column="employee_id")
term = models.ForeignKey(SuccessionTerm)
class Position(models.Model):
positionId = models.CharField(max_length=200, unique=True, db_column="positionid")
title = models.CharField(max_length=200, null=True)
# There cannot be a DB constraint, as that would make it impossible to add the first position.
dottedLine = models.ForeignKey("Position", to_field='positionId', related_name="Dotted Line",
null=True, db_constraint=False, db_column="dottedline_id")
solidLine = models.ForeignKey("Position", to_field='positionId', related_name="SolidLine",
null=True, db_constraint=False, db_column="solidline_id")
grade = models.ForeignKey(Grade)
businessUnit = models.ForeignKey(BusinessUnit, null=True, db_column="businessunit_id")
functionalArea = models.ForeignKey(FunctionalArea, db_column="functionalarea_id")
location = models.ForeignKey(Location, db_column="location_id")
class Employee(models.Model):
person = models.OneToOneField(Person, db_column="person_id")
fte = models.IntegerField(default=100)
dataSource = models.ForeignKey(DataSource, db_column="datasource_id")
talentStatus = models.ForeignKey(TalentStatus, db_column="talentstatus_id")
retentionRisk = models.ForeignKey(RetentionRisk, db_column="retentionrisk_id")
retentionRiskReason = models.ForeignKey(RetentionRiskReason, db_column="retentionriskreason_id")
performanceStatus = models.ForeignKey(PerformanceStatus, db_column="performancestatus_id")
potential = models.ForeignKey(Potential, db_column="potential_id")
mobility = models.ForeignKey(Mobility, db_column="mobility_id")
currency = models.ForeignKey(Currency, null=True, db_column="currency_id")
grade = models.ForeignKey(Grade, db_column="grade_id")
position = models.OneToOneField(Position, to_field='positionId', null=True,
blank=True, db_column="position_id")
employeeId = models.CharField(max_length=200, unique=True, db_column="employeeid")
dateInPosition = models.DateField(null=True, db_column="dateinposition")
Now, what I want is for each employee to get the position title, the person's name, and for each succession term (of which there are three) how many times the position of that employee is in the succession table, and the number of times each of these employees occurs in the successors table. Above all, I want to do all of this in a singe query (or more specifically, a single Django ORM statement), as I'm doing this in a paginated way, but I want to be able to order the result on any of these columns!
So far, I have this:
emps = Employee.objects.all()
.annotate(ls_st=Count('succession__term'))
.filter(succession__term__description="ShortTerm")
.order_by(ls_st)
.prefetch_related('person', 'position')[lower_limit:upper_limit]
This is only one of the succession terms, and I would like to extend it to all terms by adding more annotate calls.
My problem is that the filter call works on the entire query. I would like to only filter on the Count call.
I've tried doing something like Count(succession__term__description'="ShortTerm") but that doesn't work. Is there any other way to do this?
Thank you very much in advance,
Regards,
Linus
So what you want is a count of each different type of succession__term? That is pretty complex, and I don't think you can do this with the built in django orm right now. (unless you did a .extra() query)
In django 1.8, I believe you will be able to do it with the new Query Expressions (https://docs.djangoproject.com/en/dev/releases/1.8/#query-expressions). But of course 1.8 isn't released yet, so that doesn't help you.
In the meantime, you can use the very handy django-aggregate-if package. (https://github.com/henriquebastos/django-aggregate-if/, https://pypi.python.org/pypi/django-aggregate-if)
With django-aggregate-if, your query might look like this:
emps = Employee.objects.annotate(
ls_st=Count('succession__term', only=Q(succession__term__description="ShortTerm")),
ls_lt=Count('succession__term', only=Q(succession__term__description="LongTerm")), # whatever your other term descriptions are.
ls_ot=Count('succession__term', only=Q(succession__term__description="OtherTerm"))
)
.order_by('ls_st')
.prefetch_related('person', 'position')[lower_limit:upper_limit]
Disclaimer: I have never used django-aggregate-if, so I'm not entirely sure if this will work, but according the the README, it seems like it should.

Did I set this up correctly?

I'm asking if I set up the create method up correctly. Or does it need to be added for the other two models as well? How would this be changed?
class PointModel(models.Model):
x = models.IntegerField()
y = models.IntegerField()
index = models.IntegerField()
class DatetimeRangeModel(models.Model):
start_datetime = models.CharField(max_length=14)
end_datetime = models.CharField(max_length=14)
class PlanModel(models.Model):
data_number = models.IntegerField()
data_datetime_range = models.ForeignKey(DatetimeRangeModel, blank=True, null=True, on_delete=models.SET_NULL)
data_polygon = models.ForeignKey(PointModel, blank=True, null=True, on_delete=models.SET_NULL)
#classmethod
def create(cls, data_number, data_datetime_range, data_polygon):
plan = cls(data_number=data_number, data_datetime_range = data_datetime_range,
data_polygon=data_polygon)
return plan
EDIT: I change the structure which fixed the undefined and added some logic that prevents the PlanModel from being deleted with the "blank=True, null=True, on_delete=models.SET_NULL"
Does this look right?
see the docs for creating objects
#classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
there's no much reason to add those unless you have something to add there on the # do something with the book line
EDIT: instead of calling create you're usually do:
plan = PlanModel(data_number=1, ....)
plan.save()
or sometimes:
plan = PlanModel()
plan.data_number=1
...
plan.save()

django check if a model table has a object

i have a model like bellow .
class User(BaseModel,DjangoUser):
uuid = models.CharField(max_length=100, blank=True)
realname = models.CharField(max_length=100, blank=True)
followers = models.ManyToManyField('self', through='FollowRelationship', symmetrical=False, related_name='followed_by')
def follow(self, person):
relationship, created = FollowRelationship.objects.get_or_create(from_person=self,to_person=person)
return created
def unfollow(self, person):
fs = FollowRelationship.objects.filter(from_person=self,to_person=person)
if fs is not None:
fs.delete()
return True
return False
class FollowRelationship(models.Model):
from_person = models.ForeignKey(User, related_name='from_user')
to_person = models.ForeignKey(User, related_name='to_user')
follow_time = models.DateTimeField(default=datetime.datetime.now)
if i have two uses A and B , i use A in B.followers.all() to check if A is following B .
is it good to do this , if B.followers have 10000000 objects , is it a good way to do this ?
another question is that if i do follow a User in my (android) application , the A in B.followers.all() will return false for sometime and return true . if i restart the django server , it will become true immediately。 what's the problem ?
From documentation it seems .exists() is more appropriate and faster.
You can check as
if B.followers.filter(from_person=A).exists():
For 2nd question, it might be django queryset caching issue. Is it in some view?

Categories