Django OneToOne field to self - python

How to define OneToOne relationship to the same Model?
I have a model called Order which can be paired with another one Order. Now I'm trying to figure out how to handle models for this relationship.
My ideas:
class Order(models.Model):
paired_order = models.OneToOneField(self)
OR:
class Pairing(models.Model):
order1 = models.ForeignKey(Order, related_name='pairing')
order2 = models.ForeignKey(Order, related_name='pairing')
What do you think? Which is more efficient?
I want to have simple calling of paired ones. So I would do something like:
order.paired_order
OR:
order.pairing.paired
I want this relation symmetrical so for each pair of orders I call the same thing and get paired order.
Pairing model would be a good solution because I can add additional information to this relationship, but there is a problem that I would have to detect which order is it, so I couldn't call order.pairing.order1 because I don't know whether I'm not calling the same order.
EDIT:
>>> from _app import models
>>> order1 = models.Order(flight_number="xxx")
>>> order2 = models.Order(flight_number="yyy", paired_order=order1)
>>> order1.paired_order.flight_number
RETURNS None object has not ....
The problem is that when I set order1 is a paired order for order2, I want the same thing in opposite direction. So order1.paired_order = order2 do this as well order2.paired_order = order1.

Pairing model would be a good solution because I can add additional
information to this relationship.
In that case, you could model that group of "orders" (you've called it Pairing) and add a shortcut to retrieve the paired order.
class OrderPair(models.Model):
pass
# additional information goes here
class Order(models.Model):
pair = models.ForeignKey(to="OrderPair", related_name="orders")
# you'll have to add custom validation
# to make sure that only 2 orders can belong to the same "OrderPair"
#property
def paired_order(self):
return self.pair.orders.exclude(id=self.id).first()
Once you've got this working, you might also want to cache the paired order to avoid too many queries. In that case, you don't want a related name so you can use + (the less explicit thing in Django ever).
class Order(models.Model):
...
cached_paired_order = models.ForeignKey(to='self', related_name='+')
#property
def paired_order(self):
if self.cached_paired_order:
...
else:
...

Having this problem myself, the term 'symmetrical' was key to finding the answer: https://code.djangoproject.com/ticket/7689
class Order(models.Model):
paired_order = models.OneToOneField(self)
def save(self, *args, **kwargs):
super(Order, self).save(*args, **kwargs)
self.paired_order.paired_order = self
super(Order, self.paired_order).save()

The ForeignKey accepts as an argument not just a class, but also a string name of the form ForeignKey('ModelNameInSameModelsPyFile') or ForeignKey('app_name.ModelName).
In your case, it could be like
class Order(models.Model):
paired = models.ForeignKey('Order', null=True)
You can read more at https://docs.djangoproject.com/en/1.8/ref/models/fields/#foreignkey

Related

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.

How to generate feed from different models in Django?

So, I have two models called apartments and jobs. It's easy to display contents of both models separately, but what I can't figure out is how to display the mix feed of both models based on the date.
jobs = Job.objects.all().order_by('-posted_on')
apartments = Apartment.objects.all().order_by('-date')
The posted date on job is represented by 'posted_by' and the posted date on apartment is represented by 'date'. How can I combine both of these and sort them according to the date posted? I tried combining both of these models in a simpler way like:
new_feed = list(jobs) + list(apartments)
This just creates the list of both of these models, but they are not arranged based on date.
I suggest two ways to achieve that.
With union() New in Django 1.11.
Uses SQL’s UNION operator to combine the results of two or more QuerySets
You need to to make sure that you have a unique name for the ordered field
Like date field for job and also apartment
jobs = Job.objects.all().order_by('-posted_on')
apartments = Apartment.objects.all().order_by('-date')
new_feed = jobs.union(apartments).order_by('-date')
Note with this options, you need to have the same field name to order them.
Or
With chain(), used for treating consecutive sequences as a single sequence and use sorted() with lambda to sort them
from itertools import chain
# remove the order_by() in each queryset, use it once with sorted
jobs = Job.objects.all()
apartments = Apartment.objects.all()
result_list = sorted(chain(job, apartments),
key=lambda instance: instance.date)
With this option, you don't really need to rename or change one of your field names, just add a property method, let's choose the Job Model
class Job(models.Model):
''' fields '''
posted_on = models.DateField(......)
#property
def date(self):
return self.posted_on
So now, both of your models have the attribute date, you can use chain()
result_list = sorted(chain(job, apartments),
key=lambda instance: instance.date)
A good way to do that is to use adapter design pattern. The idea is that we introduce an auxiliary data structure that can be used for the purpose of sorting these model objects. This method has several benefits over trying to fit both models to have the identically named attribute used for sorting. The most important is that the change won't affect any other code in your code base.
First, you fetch your objects as you do but you don't have to fetch them sorted, you can fetch all of them in arbitrary order. You may also fetch just top 100 of them in the sorted order. Just fetch what fits your requirements here:
jobs = Job.objects.all()
apartments = Apartment.objects.all()
Then, we build an auxiliary list of tuples (attribute used for sorting, object), so:
auxiliary_list = ([(job.posted_on, job) for job in jobs]
+ [(apartment.date, apartment) for apartment in apartments])
now, it's time to sort. We're going to sort this auxiliary list. By default, python sort() method sorts tuples in lexicographical order, which mean it will use the first element of the tuples i.e. posted_on and date attributes for ordering. Parameter reverse is set to True for sorting in decreasing order i.e. as you want them in your feed.
auxiliary_list.sort(reverse=True)
now, it's time to return only second elements of the sorted tuples:
sorted_feed = [obj for _, obj in auxiliary_list]
Just keep in mind that if you expect your feed to be huge then sorting these elements in memory is not the best way to do this, but I guess this is not your concern here.
I implemented this in the following ways.
I Video model and Article model that had to be curated as a feed. I made another model called Post, and then had a OneToOne key from both Video & Article.
# apps.feeds.models.py
from model_utils.models import TimeStampedModel
class Post(TimeStampedModel):
...
#cached_property
def target(self):
if getattr(self, "video", None) is not None:
return self.video
if getattr(self, "article", None) is not None:
return self.article
return None
# apps/videos/models.py
class Video(models.Model):
post = models.OneToOneField(
"feeds.Post",
on_delete=models.CASCADE,
)
...
# apps.articles.models.py
class Article(models.Model):
post = models.OneToOneField(
"feeds.Post",
on_delete=models.CASCADE,
)
...
Then for the feed API, I used django-rest-framework to sort on Post queryset's created timestamp. I customized serializer's method and added queryset annotation for customization etc. This way I was able to get either Article's or Video's data as nested dictionary from the related Post instance.
The advantage of this implementation is that you can optimize the queries easily with annotation, select_related, prefetch_related methods that works well on Post queryset.
# apps.feeds.serializers.py
class FeedSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ("type",)
def to_representation(self, instance) -> OrderedDict:
ret = super().to_representation(instance)
if isinstance(instance.target, Video):
ret["data"] = VideoSerializer(
instance.target, context={"request": self.context["request"]}
).data
else:
ret["data"] = ArticleSerializer(
instance.target, context={"request": self.context["request"]}
).data
return ret
def get_type(self, obj):
return obj.target._meta.model_name
#staticmethod
def setup_eager_loading(qs):
"""
Inspired by:
http://ses4j.github.io/2015/11/23/optimizing-slow-django-rest-framework-performance/
"""
qs = qs.select_related("live", "article")
# other db optimizations...
...
return qs
# apps.feeds.viewsets.py
class FeedViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = FeedSerializer
permission_classes = (IsAuthenticatedOrReadOnly,)
def get_queryset(self):
qs = super().get_queryset()
qs = self.serializer_class().setup_eager_loading(qs)
return as
...

Specialized django query

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)

How do you order lists in the same way QuerySets are ordered in Django?

I have a model that has an ordering field under its Meta class. When I perform a query and get back a QuerySet for the model it is in the order specified. However if I have instances of this model that are in a list and execute the sort method on the list the order is different from the one I want. Is there a way to sort a list of instances of a model such that the order is equal to that specified in the model definition?
Not automatically, but with a bit of work, yes. You need to define a comparator function (or cmp method on the model class) that can compare two model instances according to the relevant attribute. For instance:
class Dated(models.Model):
...
created = models.DateTimeField(default=datetime.now)
class Meta:
ordering = ('created',)
def __cmp__(self, other):
try:
return cmp(self.created, other.created)
except AttributeError:
return cmp(self.created, other)
The answer to your question is varying degrees of yes, with some manual requirements. If by list you mean a queryset that has been formed by some complicated query, then, sure:
queryset.order_by(ClassName.Meta.ordering)
or
queryset.order_by(instance._meta.ordering)
or
queryset.order_by("fieldname") #If you like being manual
If you're not working with a queryset, then of course you can still sort, the same way anyone sorts complex objects in python:
Comparators
Specifying keys
Decorate/Sort/Undecorate
See the python wiki for a detailed explanation of all three.
Building on Carl's answer, you could easily add the ability to use all the ordering fields and even detect the ones that are in reverse order.
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birthday = date = models.DateField()
class Meta:
ordering = ['last_name', 'first_name']
def __cmp__(self, other):
for order in self._meta.ordering:
if order.startswith('-'):
order = order[1:]
mode = -1
else:
mode = 1
if hasattr(self, order) and hasattr(other, order):
result = mode * cmp(getattr(self, order), getattr(other, order))
if result: return result
return 0

Categories