Specialized django query - python

As you can guess from the title, I'm not exactly sure how to describe what I want. Please take a look at the following classes:
from django.db import models
from django.contrib.auth.models import User as Auth_User
class User(Auth_User):
Portfolio = models.ManyToManyField('PortfolioItem', through='SkillTag')
Age = models.IntegerField(blank=False)
#property
def full_name(self):
return self.first_name + ' ' + self.last_name
def __unicode__(self):
return self.full_name
class PortfolioItem(models.Model):
Title = models.CharField(max_length=200, blank=False)
class SkillTag(models.Model):
User = models.ForeignKey('User')
PortfolioItem = models.ForeignKey('PortfolioItem')
Tag_Name = models.CharField(max_length=200, blank=False)
What I need to do, is for every user, get all the Tag_Name values of it's SkillTags, how do I do this?

You can do something like this
class User(Auth_User):
#other attributes
def tag_names(self):
return self.skilltag_set.values_list('Tag_Name', flat=True)
So, here, we are doing a couple of things:
Querying in reverse ForeignKey relationship.
Since you are not using a related_name in the ForeignKey attribute, by default django would assign the model name (lowercase) followed by _set attribute, which makes it .skilltag_set.all()
values_list
Returns a ValuesQuerySet — a QuerySet subclass that returns tuples when used as an iterable, rather than model-instance objects.
Example: [('a'), ('b'), ('c')]
Basically, you are retriving an iterable of ValuesQuerySet (think of it as a list or any other iterables) consisting of tuples.
flat=True
This basically flattens the on-tuples into single values.
Example: ['a', 'b', 'c']

most obvious: using the reverse relationship of ForeignKey fields:
def skill_names_1(user):
return [t.name for t in user.skilltag_set.all()]
The same thing, but explicitly selecting for the user. also, it fetches only the required field from the database.
def skill_names_2(user):
return SkillTag.objects.filter(User=user).values_list('Tag_Name',flat=True)
Either of these can also work as a method of User. Of course, typically the argument would be called self instead of user.
All the skills for a group of users:
def skill_names_3(users):
return SkillTag.objects.filter(User__in=users).values_list('Tag_Name',flat=True)

Related

Django: How to use an annotation from a parent queryset?

I have an Item class which can be annotated using a custom queryset add_is_favorite_for method:
class ItemQuerySet(QuerySet):
def add_is_favorite_for(self, user):
"""add a boolean to know if the item is favorited by the given user"""
condition = Q(id__in=Item.objects.filter(favoriters=user).values("id"))
return self.annotate(is_favorite=Condition(condition)) # True or False
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
It works as expected. For example:
>>> user = User.objects.get(id=1)
>>> Item.objects.add_is_favorite_for(user) # each item has now a `is_favorite` field
Then, I added a Factory model and link Item model to it using a 1->N relationship:
class Factory(Model):
pass # ...
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
advised_in = models.ForeignKey(
Factory,
on_delete=models.CASCADE,
related_name="advised_items",
)
Now, I'd like to be able to return a Factory QuerySet, whose advised_items fields will all contain the is_favorite annotation too.
I don't know how to do this, I saw no example of such a thing in the doc, maybe I missed it.
You can work with a Prefetch object [Django-doc]:
from django.db.models import Prefetch
Factory.objects.prefetch_related(
Prefetch('advised_items', queryset=Item.objects.add_is_favorite_for(some_user))
)

Display custom format name on django admin page

I want to display first name and last name. To achieve that, I have used __str__() method. But it is not working.
class OnlineUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.user.first_name} {self.user.last_name}"
Instead it display, user ID.
In admin.py,
class OnlineUsersAdmin(admin.ModelAdmin):
# a list of displayed columns name.
list_display=['user']
admin.site.register(OnlineUsers, OnlineUsersAdmin)
Where I'm doing wrong? How to get the format I want?
Version:
Django==2.0.1
Debug:
user_info=OnlineUsers.objects.get_or_create(user=self.user)
print(user_info.__str__())
Output:
((<OnlineUsers: FIRST_NAME LAST_NAME >, False))
You should use __str__ instead of user in list_display:
list_display=['__str__']
Otherwise you tell django to show user field. And since User model doen't have overrided __str__ method you see user's id.
Also you can just remove list_display attribute. In this case __str__ will be used by default.
The list_display--(Django doc) support callable methods too. So, define a user(...) method on Model admin as,
class OnlineUsersAdmin(admin.ModelAdmin):
# a list of displayed columns name.
list_display = ['user']
def user(self, instance):
return instance.__str__()
Here, inside return you can return only str + str, not str + int. So, you have to convert every int to str
from django.db import models
# Create your models here.
class Fabonacci(models.Model):
numstr = models.IntegerField()
terms = models.CharField(max_length=500)
def __str__(self):
return "The first " + str(self.numstr) + " terms in fibonacci series : " + self.terms

Django Admin: Custom ordering according to concatenated charfields of a related manytomany model

Here are my simplified models:
class MasterFood(models.Model):
class Meta:
verbose_name_plural = 'Master Foods'
FOOD_TYPE_CHOICES = (
('F', 'Fats'),
('C', 'Carbohydrates'),
('P', 'Proteins'),
('O', 'Others'),
)
food_name = models.CharField(max_length=255)
food_type = models.CharField(max_length=20,choices=FOOD_TYPE_CHOICES)
def __str__(self):
return self.food_name
class MasterMeal(models.Model):
class Meta:
verbose_name_plural = 'Master Meal Plan Meals'
proteins = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'P'},blank=True,related_name='food_proteins')
carbohydrates = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'C'}, blank=True, related_name='food_carbohydrates')
fats = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'F'}, blank=True, related_name='food_fats')
def all_foods(self):
return list(self.proteins.all())+list(self.carbohydrates.all())+list(self.fats.all())
def __str__(self):
return ', '.join(map(lambda x: x.food_name, self.all_foods()))+f' ({len(self.all_foods())})'
and my modelAdmin object in admin.py:
class MealAdmin(admin.ModelAdmin):
model = MasterMeal
save_as = True
ordering = ('proteins__food_name','carbohydrates__food_name','fats__food_name',)
What I would like is in the Django admin page for MasterMeals is to have each object named after the __str__ method given in my MasterMeal model, but in a different ordering than the default.
Specifically, I would like the objects to be sorted in alphabetical order according to the __str__ method definition, and if possible, I don't want to modify my MasterMeal model to add another field to achieve this. I have tried several things such as the ordering = ('proteins__food_name','carbohydrates__food_name','fats__food_name',) in my MealAdmin object as well as ordering = ('proteins','carbohydrates','fats',). I have also tried overwriting the queryset of the ModelAdmin with an annotate function like:
class MealAdmin(admin.ModelAdmin):
model = MasterMeal
save_as = True
def get_queryset(self,request):
qs = super(MealAdmin,self).get_queryset(request)
return qs.distinct() \
.annotate(protein_order=F('proteins__food_name'),carb_order = F('carbohydrates__food_name'),fat_order = F('fats__food_name')) \
.order_by('protein_order', 'carb_order','fat_order')
but I am clearly not annotating the objects in the way that they need to be in order to get the ordering that I am after. All 3 of these examples produce this exact same ordering:
Incorrect Ordering,
You can see an object with the exact same name has several other entries in between it. The objects are not sorted in alphabetical order according to the __str__ method, and are instead, (I believe?) being sorted on keys that have been generated for each of the groups of proteins/carbs/fats.
I did read here in the Django docs in regard to ModelAdmin.ordering that:
'To ensure a deterministic ordering of results, the changelist adds pk to the ordering if it can’t find a single or unique together set of fields that provide total ordering.' But I do not know how to modify my code to get it to order these objects in the order I DO want.
I believe I could add a non-editable field to my MasterMeal model, and overwrite the save() method for the model as shown here, in order to auto-populate the field with my desired string, which I believe I could then order on, but I don't want to have to change my model and have to update all of my objects on all of my servers to add this field.
Can anyone tell me if there is a way I can achieve this ordering?
I am very new to Django, and have limited experience with Python, so thank you very much for any help you can provide.
You can do something like this:
class MasterMeal(models.Model):
class Meta:
verbose_name_plural = 'Master Meal Plan Meals'
proteins = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'P'},blank=True,related_name='food_proteins')
carbohydrates = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'C'}, blank=True, related_name='food_carbohydrates')
fats = models.ManyToManyField(to="MasterFood", limit_choices_to={'food_type': 'F'}, blank=True, related_name='food_fats')
def all_foods_string(self):
proteins = self.proteins.all().values_list('food_name', flat=True)
carbs = self.carbohydrates.all().values_list('food_name', flat=True)
fats = self.fats.all().values_list('food_name', flat=True)
return sorted(proteins + carbs + fats)
def __str__(self):
all_foods = self.all_foods_string()
return ', '.join(all_foods)) + f' ({len(all_foods)})'
I changed the query for each food to only return the string value of the food_name field to avoid making full query of the object if we only need one field (this is very useful).
I found that it wasn't possible to do what I wanted since I did not have a field to order on and I was not able to use a query expression to do what I needed. I chose instead to create a new CharField in my model, name, which I auto populated with my __str__ method when left blank. See the solution here.

check related object or not

I need to check my object related or not ORM postgresql/
I have two object ItemVariationValue and ItemVariation i need to check
ItemVariationValue has relation with ItemVariation
models.py
class ItemVariation(models.Model):
item=models.ForeignKey(Item,on_delete=models.CASCADE)
price=models.IntegerField(blank=True,null=True,default=0)
item_code=models.CharField(max_length=500)
keywords= models.ManyToManyField(Keyword)
image=models.ImageField(upload_to='dishes/', blank=True, null=True)
def __str__(self):
return str(self.id)
class ItemVariationValue(models.Model):
item=models.ForeignKey(Item,on_delete=models.CASCADE)
item_variation=models.ForeignKey(ItemVariation,on_delete=models.CASCADE)
attribute=models.ForeignKey(Attribute,on_delete=models.CASCADE)
attribute_value=models.ForeignKey(AttributeValue,on_delete=models.CASCADE)
def __str__(self):
return str(self.id)
views.py
def get_items(request): # request= {"order_details": "varxxx:1"}
order=request.data['order_details']
items = order.split(',')
total = 0
for item in items:
od = item.split(':')
sku = str(od[0])
qty = int(od[1])
itemvariation=ItemVariation.objects.get(item_code=sku)
# if ItemVariationValue.item_variation has ForeignKey(ItemVariation):
Note (ForeignKey nomenclature): ForeignKeys should not have an _id suffix, since Django will automatically construct an extra field fieldname_id that contains the primary key. By writing an _id suffix, you will introduce extra attributes like item_variation_id_id. Although strictly speaking that is not a problem, it introduces a lot of confusion. For example my_itemvariationvalue.itemvariation_id will result in an ItemVariation object, etc.
If you fix the ForeignKey names, you can check this like:
# given the ForeignKey is named itemvariation, not itemvariation_id and use Comparison Operators
if my_itemvariationvalue.itemvariation_id == my_itemvariation.id:
# ... (objects are related)
else:
# ... (otherwise)
By using the itemvariation_id here, we avoid loading the related object, and thus potentially an extra database query.
As far as i know you have a ItemVariationValue object and you want to know if this object has ItemVariation. so you can easily do
#your code
itemvariation=ItemVariation.objects.get(item_code=sku)
check_relation_obj=itemvariation.item_variation
also if you think you may or may not have relation with ItemVariation
add blank=True,null=True with Foreign Key.

Python convert string to class

I want to do a query on the django User table like this:
u = User.objects.filter(member__in = member_list)
where:
class Member(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
dob = models.DateField('Date of Birth', blank=True, null=True)
and member_list is a list of eligible members.
The query works fine but the problem is I do not actually know the model member is called member. It could be called anything.
I store the name of the model I want in a model called Category. I have a link to the name of the model through content_type.Category is defined as:
class Category(models.Model):
name = models.CharField('Category', max_length=30)
content_type = models.ForeignKey(ContentType)
filter_condition = JSONField(default="{}", help_text=_(u"Django ORM compatible lookup kwargs which are used to get the list of objects."))
user_link = models.CharField(_(u"Link to User table"), max_length=64, help_text=_(u"Name of the model field which links to the User table. 'No-link' means this is the User table."), default="No-link")
def clean (self):
if self.user_link == "No-link":
if self.content_type.app_label == "auth" and self.content_type.model == "user":
pass
else:
raise ValidationError(
_("Must specify the field that links to the user table.")
)
else:
if not hasattr(apps.get_model(self.content_type.app_label, self.content_type.model), self.user_link):
raise ValidationError(
_("Must specify the field that links to the user table.")
)
def __unicode__(self):
return self.name
def _get_user_filter (self):
return str(self.content_type.app_label)+'.'+str(self.content_type.model)+'.'+str(self.user_link)+'__in'
def _get_filter(self):
# simplejson likes to put unicode objects as dictionary keys
# but keyword arguments must be str type
fc = {}
for k,v in self.filter_condition.iteritems():
fc.update({str(k): v})
return fc
def object_list(self):
return self.content_type.model_class()._default_manager.filter(**self._get_filter())
def object_count(self):
return self.object_list().count()
class Meta:
verbose_name = _("Category")
verbose_name_plural = _("Categories")
ordering = ('name',)
So I can retrieve the name of the model that links to User but I then need to convert it into a class which I can include in a query.
I can create an object x = category.content_type.model_class() which gives me <class 'cltc.models.Member'> but when I them perform a query s = User.objects.filter(x = c.category.object_list()) I get the error Cannot resolve keyword 'x' into field.
Any thoughts most welcome.
The left hand side of the filter argument is a keyword, not a python object, so x is treated as 'x', and Django expects a field called x.
To get around this, you can ensure that x is a string, and then use the python **kwarg syntax:
s = User.objects.filter(**{x: c.category.object_list()})
Thanks to https://stackoverflow.com/a/4720109/823020 for this.

Categories