Fetching ManyToMany objects from multiple objects through intermediate tables - django

Is there an easy way to fetch the ManyToMany objects from a query that returns more than one object? The way I am doing it now doesn't feel as sexy as I would like it to. Here is how I am doing it now in my view:
contacts = Contact.objects.all()
# Use Custom Manager Method to Fetch Each Contacts Phone Numbers
contacts = PhoneNumber.objects.inject(contacts)
My Models:
class PhoneNumber(models.Model):
number = models.CharField()
type = models.CharField()
# My Custom Manager
objects = PhoneNumberManager()
class Contact(models.Model):
name = models.CharField()
numbers = models.ManyToManyField(PhoneNumber, through='ContactPhoneNumbers')
class ContactPhoneNumbers(models.Model):
number = models.ForeignKey(PhoneNumber)
contact = models.ForeignKey(Contact)
ext = models.CharField()
My Custom Manager:
class PhoneNumberManager(models.Manager):
def inject(self, contacts):
contact_ids = ','.join([str(item.id) for item in contacts])
cursor = connection.cursor()
cursor.execute("""
SELECT l.contact_id, l.ext, p.number, p.type
FROM svcontact_contactphonenumbers l, svcontact_phonenumber p
WHERE p.id = l.number_id AND l.contact_id IN(%s)
""" % contact_ids)
result = {}
for row in cursor.fetchall():
id = str(row[0])
if not id in result:
result[id] = []
result[id].append({
'ext': row[1],
'number': row[2],
'type': row[3]
})
for contact in contacts:
id = str(contact.id)
if id in result:
contact.phonenumbers = result[id]
return contacts

There are a couple things you can do to find sexiness here :-)
Django does not have any OOTB way to inject the properties of the through table into your Contact instance. A M2M table with extra data is a SQL concept, so Django wouldn't try to fight the relations, nor guess what should happen in the event of namespace collision, etc... . In fact, I'd go so far as to say that you probably do not want to inject arbitrary model properties onto your Contact object... if you find yourself needing to do that, then it's probably a sign you should revise your model definition.
Instead, Django provides convenient ways to access the relation seamlessly, both in queries and for data retrieval, all the while preserving the integrity of the entities. In this case, you'll find that your Contact object offers a contactphonenumbers_set property that you can use to access the through data:
>>> c = Contact.objects.get(id=1)
>>> c.contactphonenumbers_set.all()
# Would produce a list of ContactPhoneNumbers objects for that contact
This means, in your case, to iterate of all contact phone numbers (for example) you would:
for contact in Contact.objects.all():
for phone in contact.contactphonenumbers_set.all():
print phone.number.number, phone.number.type, phone.ext
If you really, really, really want to do the injection for some reason, you'll see you can do that using the 3-line code sample immediately above: just change the print statements into assignment statements.
On a separate note, just for future reference, you could have written your inject function without SQL statements. In Django, the through table is itself a model, so you can query it directly:
def inject(self, contacts):
contact_phone_numbers = ContactPhoneNumbers.objects.\
filter(contact__in=contacts)
# And then do the result construction...
# - use contact_phone_number.number.phone to get the phone and ext
# - use contact_phone_number.contact to get the contact instance

Related

Is it possible to link multiple models to one fiel in django?

Let's say I have these models:
class Material(models.Model):
name = models.CharField([...])
class Consumable(models.Model):
name = models.CharField([...])
restores = models.IntegerField([...])
class Weapon(models.Model):
name = models.CharField([...])
damage = models.IntegerField([...])
# And then I have an 'inventory', like this one:
class Inventory(models.Model):
user = models.ForeignKey([...]) # to which user you want to link the item
item = models.ForeignKey([...]]) # which item
quantity = models.IntegerField([...]) # how many of it
I want to be able to have all Material, Consumable, and Weapon models listed in the 'item' field, so when you want to add an item as an inline, you would see all 3 models' objects.
Something like
# instead of this
item = models.ForeignKey(Consumable) # which item
# want something like this
item = models.ForeignKey(Consumable and Material and Weapon) # which item
# this wouldn't work ofc...
Is there a way to collect all 3 of them and pass them to the 'item' field, without the need of restarting the server? (when making a "choices" list that queries from a model you must restart the server to see the newly added objects, I don't want that.)
I also want to stick to the built-in admin of Django since it provided everything I need for the past couple of months, but I am open to any ideas.
I could be wrong but I think you are making this more complex than it needs to be. Instead of doing separate classes for materials (type of material) and consumable (type of product), you can have that built in the last class as model field as category or bolean fields.
class Products(models.Model):
material_type =
consumable = boolean for yes no or you can do multiple choice field
Then for items you can query the number of items based on material_type or consumable model fields (see query filters for for more).
all_items = Products.model.all()
consumable_items = Products.model.filter(your filter logic goes here)
Hope this helps!

related field nested lookup error

I have the following models:
class Profile(models.Model):
user = models.ForeignKey(User)# User can have more than one profile
class Order(models.Model):
ship_to = models.ForeignKey(Profile)
class Shipping(models.Model):
order = models.ForeignKey(Order)# one order can have more than one shipping
shipping_company = models.ForeignKey(Shipping_company)
class Shipping_company(models.Model):
name = ...
So now i have the following structure:
User > Receiver > Order > Shipping > Shipping_company
The question is: How can i get all User models, who ordered with specific Shipping company?
If i make a query like this
User.objects.filter(receiver__order__shipping__shipping_company__pk=1)
i get
FieldError: Relation fields do not support nested lookups
if i make something like this
sh_comp = items.objects.get(pk=1) # __unicode__ returns "FedEx"
User.objects.filter(receiver__order__shipping__shipping_company=sh_comp)
the result is
ValueError: Cannot query "FedEx": Must be "Receiver" instance.
This seemed to be a simple and trivial task, but i can't make it work.
One approach that can be taken is as following(I am only considering the four models you have presented in your question),
You have foreign key of Shipping company in Shipping model. So you can make use of model function here on Shipping_company model.
Take a look at this model function
class Shipping_company(models.Model):
fields...
def get_profiles(self):
shippings = Shipping.objects.filter(shipping_company=self)
users = list(set([x.order.ship_to for x in shippings]))
Explanation:
shippings = Shipping.objects.filter(shipping_company=self)
will return all the shippings for one Shipping company(FedEx in your case). Further loop through the shippings to get ship_to from order field.
PS: You can take it as reference and design your own solution.
Walkthrough:
Lets say there is shipping company 'FedEx'. So we do,
fedex = Shipping_company.objects.get(name='FedEx')
Now, when you call get_profiles on fedex, like
fedex.get_profiles()
what will happen is this.
fedex instance refers to self in get_profiles() function now.
Using self(fedex), we filter out shippings by fedex.
Then we loop through those shippings to get order per shipping and each of that order has a ship_to(profile) foreign key.
I guess, you are getting confused because of the return statement.
In elaborate fashion the whole function will look something like this
def get_profiles(self):
users = list()
shippings = Shipping.objects.filter(shipping_company=self)
for shipping in shippings:
order = shipping.order
#Now you have an order per shipping, so you do
if not order.ship_to in users:
users.append(order.ship_to)
return users

Custom SQL for Geodjango on ForignKey

I have a following model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
location = models.PointField(blank=True, null=True, srid=CONSTANTS.SRID)
objects = models.GeoManager()
class Item(models.Model):
owner = models.ForeignKey(UserProfile)
objects = models.GeoManager()
Now I need to sort the Items by distance to some point:
p = Point(12.5807203, 50.1250706)
Item.objects.all().distance(p, field='owner__location')
But that throws me an error:
TypeError: ST_Distance output only available on GeometryFields.
From GeoDjango GeoQuerySet.distance() results in 'ST_Distance output only available on GeometryFields' when specifying a reverse relationship in field_name I can see there is already ticket for this.
Now I don't like the solution proposed in that question since that way I would not get the distance and I would lose the distances.
So I was thinking that I could achieve this by making a custom sql query. I know that this:
UserProfile.objects.distance(p)
will produce something like this:
SELECT (ST_distance_sphere("core_userprofile"."location",ST_GeomFromEWKB('\x0101000020e6100000223fd12b5429294076583c5002104940'::bytea))) AS "distance", "core_userprofile"."id", "core_userprofile"."user_id", "core_userprofile"."verified", "core_userprofile"."avatar_custom", "core_userprofile"."city", "core_userprofile"."location", "core_userprofile"."bio" FROM "core_userprofile"
So my question is: is there some easy way how to manually construct such query that would sort items by distance?
Since the geometry you're measuring distance to is on UserProfile, it makes sense to query for UserProfile objects and then handle each Item object they own. (The distance is the same for all items owned by a profile.)
For example:
all_profiles = UserProfile.objects.all()
for profile in all_profiles.distance(p).order_by('distance'):
for item in profile.item_set.all():
process(item, profile.distance)
You may be able to make this more efficient with prefetch_related:
all_profiles = UserProfile.objects.all()
all_profiles = all_profiles.prefetch_related('item_set') # we'll need these
for profile in all_profiles.distance(p).order_by('distance'):
for item in profile.item_set.all(): # items already prefetched
process(item, profile.distance)
If it's important for some reason to query directly for Item objects, try using extra:
items = Item.objects.all()
items = items.select_related('owner')
distance_select = "st_distance_sphere(core_userprofile.location, ST_GeomFromEWKT('%s'))" % p.wkt
items = items.extra({'distance': distance_select})
items = items.order_by('distance')
Raw queries are another option, which let you get model objects from a raw SQL query:
items = Item.objects.raw("SELECT core_item.* FROM core_item JOIN core_userprofile ...")

exclude a query result in another query

Here i want to do is that ,i want to list all the person who didn't blocked me.Here in the table Blocked there is two columns name
who and whose . In whose column i store the id of the person whom i blocked and in the who column i store my id. Now i want to do that, when the blocked person click on
view-person button in my web page he cannot see profile of the person one who blocked him.
when i did this query blocked_list = Blocked.objects.filter(whose = user_id). Now i got the list of the persons who blocked me. Now i want to exclude all this person from this query total_profiles = persons.objects.all().exclude(blocked_list). How can i do this.
models.py
class persons(models.Model):
full_name = models.CharField(max_length=200)
class blocked(models.Model):
who = models.ForeignKey(persons)
whose = models.IntegerField(null=True)
views.py
def blocked(request):
blocked_list = Blocked.objects.filter(whose = user_id)
total_profiles = persons.objects.all().exclude(blocked_list)
return render_to_response('profiles/view_all.html', {'total_profiles':total_profiles,'}, context_instance=RequestContext(request),)
please correct the question if it is not correct.
You can try this:
total_profiles = persons.objects.all().exclude(id__in = blocked_list.values_list('id', flat=True))
It's untested, but adapted from this answer.
Some notes:
if persons has the default manager, you can omit all().
whose does not have an index, so it will become slow when your dataset gets big. You can use a ForeignKey field instead of an IntegerField
the common convention is to capitalize class names and to write model names in singular i.e. Person instead of persons

Why is get get_queryset() not returning the right results?

I have two models, Recieved_order and order,
class Order(SmartModel):
restaurant = models.ForeignKey(Restaurant,null=True,blank=True,default = None,help_text="The restaurant the customer order from")
#contact info
email = models.EmailField(max_length=50,help_text="Needed as alternative")
mobile = PhoneNumberField(max_length=20,default='+25078######')
class Recieved_Order(SmartModel):
item = models.ForeignKey(Item)
date_added = models.DateTimeField(auto_now=True,auto_now_add=True)
quantity = models.IntegerField(default=0)
price = models.DecimalField(max_digits=9,decimal_places=2)
order = models.ForeignKey(Order)
i want a restaurant manager(user), to be able to receive orders(Recieved_order) made to his specific restaurants when logged in, to achieve this, i have the following in views.py
class Recieved_OrderCRUDL(SmartCRUDL):
model = Recieved_Order
actions = ('create','read','update','delete','list')
permissions = True
class List(SmartListView):
fields = ('order_email','order_mobile','order_billing_city','item.name','item.price','quantity','order_id','order_restaurant')
search_fields = ('date_added',)
def get_queryset(self,*args,**kwargs):
queryset = super(Recieved_OrderCRUDL.List, self).get_queryset(*args,**kwargs)
if self.request.user.is_superuser:
return queryset
return queryset.filter(order=self.request.user)
with the above i am testing on two different restaurants, the restaurant and its not working out as it should. its returning the wrong orders for a given restaurant.
What am i not doing right with get_queryset().
There's something confusing going on here:
return queryset.filter(order=self.request.user)
You're telling it to build a query that filters Order objects against User objects.
Is there something missing in your sample code that ties orders back to users such that a proper join can be constructed?
If you want to have a user (what you refer to as a manager) only able to view their own orders, you need to change things... Restaurant will need to have a field that points to a User (let's call it user and assume it's a ForeignKey) Then you can do something like
if self.request.user.is_superuser:
return queryset
return queryset.filter(order__restaurant__user=self.request.user)
As pointed out by #Joe Holloway, you should not be trying to filter on the order field with a user object...
The other odd thing I wanted to point out is
fields = ('order_email','order_mobile','order_billing_city','item.name','item.price','quantity','order_id','order_restaurant')
You appear to be using a mixture of ways to attempt to access things...
You should be using __ (that's 2 underscores) to access relations, not _ or .