I use Django 1.9 and Python 2.7.
My app has four models. Each "trip" is made of several "steps" chosen by the visitor, which relate to "places", which may have several related "Picture".
class Trip(models.Model):
title = models.CharField(max_length=140, blank=True)
class Step(models.Model):
theplace = models.ForeignKey(ThePlace)
trip = models.ForeignKey(Trip)
class ThePlace(models.Model):
name = models.CharField(max_length=200)
class Picture(models.Model):
file = models.ImageField(upload_to="pictures")
slug = models.SlugField(max_length=100, blank=True)
theplace = models.ForeignKey(ThePlace, null=True, blank=True)
I would like to retrieve all "Picture" objects which are related to a specific Trip, using an existing "selectedtrip" queryset:
selectedtrip = Trip.objects.filter(author=request.user)[0]
pictures = selectedtrip.step_set.all().theplace.picture_set.all()
Django displays the following error:
"AttributeError: 'QuerySet' object has no attribute 'theplace'"
Any idea why ?
Because all() returns a queryset, which is a collection of items; theplace is an attribute on an individual Step, not on the collection.
The way to do this type of query is to start from the class you want to retrieve, and follow the relationships within the query using the double-underscore syntax. So:
Picture.objects.filter(theplace__step__trip=selectedtrip)
Related
I am trying to query a model against a field on a ForeignKey object's property.
I have the following models:
class Song(models.Model):
name = models.CharField(max_length=20)
limit = models.IntegerField()
class Recording(models.Model):
song = models.ForeignKey(Song, on_delete=models.CASCADE)
status = models.CharField(
max_length=1,
choices=STATUS_CHOICES,
default=OPEN
)
I would like to query Songs that have Recordings with status OPEN with a count of more than 'limit' (the field on Song). Looking over the django aggregation docs I tried something along the lines of:
# View
get(self):
songs_count = Count('recording', filter=Q(recording__status='O'))
songs = Song.objects.annotate(songs_count=songs_count)
results = songs.filter(songs_count__gt=< each song.limit... >)
Can someone point the way on how to build this query?
I greatly appreciate any and all feedback.
You can work with an F object [Django-doc] to refer to a field, so:
from django.db.models import F, Q
Songs.objects.annotate(
songs_count=Count('recording', filter=Q(recording__status='O'))
).filter(songs_count__gt=F('limit'))
I have a moderation model :
class ItemModeration(models.Model):
class Meta:
indexes = [
models.Index(fields=['object_id', 'content_type']),
]
unique_together = ('content_type', 'object_id')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
published = models.BooleanField(default=False)
...
A descriptor to attach a moderation object on-the-fly :
class ItemModerationDescriptor(object):
def __init__(self, **default_kwargs):
self.default_kwargs = default_kwargs
def __get__(self, instance, owner):
ctype = ContentType.objects.get_for_model(instance.__class__)
try:
moderation = ItemModeration.objects.get(content_type__pk=ctype.id,
object_id=instance.pk)
except ItemModeration.DoesNotExist:
moderation = ItemModeration(item=instance,**self.default_kwargs)
moderation.save()
return moderation
And a model I want to moderate :
class Product(models.Model):
user = models.ForeignKey(
User,
null=True,
on_delete=models.SET_NULL)
created = models.DateTimeField(
auto_now_add=True,
blank=True, null=True,
)
modified = models.DateTimeField(
auto_now=True,
blank=True, null=True,
)
name = models.CharField(
max_length=PRODUCT_NAME_MAX_LENGTH,
blank=True, null=True,
)
moderation = ItemModerationDescriptor()
Now I can see a product 'published' state easily :
p=Product(name='my super product')
p.save()
print(p.moderation.published)
-> False
The generic relation is useful because I will be able to search the objects to moderate whatever the type is : it could be products, images, comments.
to_moderate_qs = ItemModeration.objects.filter(published=False)
Now, how can I get a filtered list of published products ?
I would like to do something like this
published_products_qs = Product.objects.filter(moderation__published=True, name__icontains='sony')
But, of course, it won't work as moderation attribute is not a Django model field.
How can I do that efficiently ? I am thinking a about an appropriate JOIN, but I cannot see how to do that with django without using raw SQL.
Django has a great built in answer for this: the GenericRelation. Instead of your descriptor, just define a generic relation on your Product model and use it as a normal related field:
from django.contrib.contenttypes.fields import GenericRelation
class Product(models.Model):
...
moderation = GenericRelation(ItemModeration)
Then handle creation as you normally would with a related model, and filtering should work exactly as you stipulated. To work as your current system, you'd have to put in a hook or save method to create the related ItemModeration object when creating a new Product, but that's no different from other related django models. If you really want to keep the descriptor class, you can obviously make use of a secondary model field for the GenericRelation.
You can also add related_query_name to allow filtering the ItemModeration objects based only on the Product content type.
WARNING if you do use a GenericRelation note that it has a fixed cascading delete behavior. So if you don't want ItemModeration object to be deleted when you delete the Product, be careful to add a pre_delete hook or equivalent!
Update
I unintentionally ignored the OneToOne aspect of the question because the GenericForeignKey is a one-to-many relation, but similar functionality can be effected via smart use of QuerySets. It's true, you don't have access to product.moderation as a single object. But, for example, the following query iterates over a filtered list of products and extracts their name, the user's username, and the published date of the related ModerationItem:
Product.objects.filter(...).values_list(
'name', 'user__username', 'moderation__published'
)
You'll have to use the content_type to query the table by specific model type.
like this:
product_type = ContentType.objects.get_for_model(Product)
unpublished_products = ItemModeration.objects.filter(content_type__pk=product_type.id, published=False)
For more details on the topic check contenttypes doc
i am developing a project with django and i have a little problem.
I am trying to get an array from a querySet of another object. Trying to obtain the "articulo" who has the "carro_det.id_articulo_fk" field, and after send it to the context of my template:
But in the querySet i am getting the error 'int() argument must be a string, a bytes-like object or a number, not 'Articulo''
Specifically in the line: articulo[i]=Articulo.objects.get(pk=carro_det[i].id_articulo_fk)
This is from my views.py:
def index(request, id_user):
carro=Carro.objects.get(id_usuario_fk=id_user)
carro_det=Carro_det.objects.filter(id_articulo_fk=carro.id)
#HERE IS THE PROBLEM
for i in range(len(carro_det)):
articulo[i]=Articulo.objects.get(pk=carro_det[i].id_articulo_fk)
contexto = {'articulo':articulo,
'carro_det':carro_det}
return render(request, 'cart/cart.html', contexto)
And this is from my models.py, as you can see everything is fine here:
class Carro(models.Model):
total = models.FloatField(default=0, null=True, blank=True)
#llaves foraneas
id_usuario_fk=models.ForeignKey('myuser.Usuario', null=True, blank=True, on_delete=models.CASCADE, db_column='id_usuario_fk')
def __str__(self):
return "id_carro_cliente: " + str(self.id)
class Carro_det(models.Model):
cantidad = models.IntegerField(default=0)
precio_venta = models.FloatField(default=0)
total = models.FloatField(default=0)
#llaves foraneas
id_carro_fk=models.ForeignKey('Carro', null=True, blank=True, on_delete=models.CASCADE, db_column='id_carro_fk')
id_articulo_fk=models.ForeignKey('article.Articulo', on_delete=models.CASCADE, db_column='id_articulo_fk')
def __str__(self):
return "numero de carro asociado: " + str(self.id_carro_fk.pk)
I hope anyone can help me with this, Thank you!.
these 2 attributes:
id_carro_fk=models.ForeignKey('Carro', null=True, blank=True, on_delete=models.CASCADE, db_column='id_carro_fk')
id_articulo_fk=models.ForeignKey('article.Articulo', on_delete=models.CASCADE, db_column='id_articulo_fk')
are objects not PK's, even though that is how you labeled them. You could do:
articulo[i]=Articulo.objects.get(pk=carro_det[i].id_articulo_fk.pk) # notice the .pk at the end
But that isn't the real problem here. It would seem you need to more carefully read the django docs on relationships. It seems like you are accessing all the related Carro_det objects to the Carro instance by making those 2 queries, when you could just access the related attribute.
When declaring a ForeignKey field in django you are accessing the related object directly, with django creating the id field under the covers. This relationship can be accessed on the other (many) side by accessing:
RelatedModel.FKmodel_set
or if specified like so:
class Carro(Model):
field = ForeignKey('Model', related_name='related_fields', ...)
then:
# Carro_det instance
instance.related_fields # this accesses all carros related to this carro_det
# but this is a queryset you can filter down further, it is fetched lazily
or in your case:
carro_instance.carro_det_set
Instead of the way you are doing it...
carro_det[i].id_articulo_fk is returning the foreignkey(Articulo) instance. Instead, you need to use id_articulo_fk_id which is the actual pk of the Articulo instance.
Use:
articulo[i]=Articulo.objects.get(pk=carro_det[i].id_articulo_fk_id)
Is there a way in Django to have multiple objects stored and manageable (in Django admin) inside another object?
Example, I have two models: Items and RMA. The RMA may have multiple Items inside of it. Each Item is unique in the sense that it is an inventoried part, so I can't just reference the same item multiple times with foreignKey (though maybe I'm misunderstanding its use/implementation).
So for now, I have an Item model:
class Item(models.Model):
serial_number = models.CharField(max_length=200)
name = models.CharField(max_length=200)
part_number = models.CharField(max_length=200)
location = models.CharField(max_length=200)
def __unicode__(self):
return self.name
And an RMA model:
class RMA(models.Model):
number = models.AutoField(primary_key=True)
items = ?????
Ultimately I'd like to be able to maintain use of the Django admin functionality to add/remove items from an RMA if necessary, so I've been staying away from serializing a list and then deserializing on display. Any help would be much appreciated.
You're modeling a has-many relationship.
This would be modeled with a Foreign Key on Item to RMA:
class Item(models.Model):
serial_number = models.CharField(max_length=200)
name = models.CharField(max_length=200)
part_number = models.CharField(max_length=200)
location = models.CharField(max_length=200)
rma = models.ForeignKey(RMA)
def __unicode__(self):
return self.name
To make it accessible in the admin of RMA you need djangos InlineAdmin functionality.
You can find examples in the django tutorial part2.
You are effectively describing a Many-To-One relation and to do this you are going to have to add the ForeignKey reference to the Item model, not to the RMA model.
You can also add a related_name to give the RMA model an attribute that you can call.
For example:
class Item(models.Model):
rma = models.ForeignKey(RMA,related_name="items")
serial_number = models.CharField(max_length=200)
# etc...
To manage the creation of these, you'll need an InlineModelAdmin form, so your admin.py file will need to look like this:
from django.contrib import admin
class ItemInline(admin.TabularInline):
model = Item
class RMAAdmin(admin.ModelAdmin):
inlines = [
ItemInline,
]
Here are my models:
class Brand(models.Model):
name = models.CharField(max_length=30)
order = models.SmallIntegerField()
class FeatureGroup(models.Model):
name = models.CharField(max_length=30)
class Feature(models.Model):
name = models.CharField(max_length=30, blank=True)
order = models.SmallIntegerField()
group = models.ForeignKey(FeatureGroup)
class FeatureDetail(models.Model):
description = models.TextField()
feature = models.ForeignKey(Feature)
class Phone(models.Model):
name = models.CharField(max_length=30)
brand = models.ForeignKey(Brand)
features = models.ManyToManyField(FeatureDetail)
I an trying to get features and feature details of the current phone and loop over data pairs but unable to do so.
I tried this and it only works for getting the brand:
p = get_object_or_404(Phone.objects.select_related('brand', 'feature__featuredetail'), id=id)
And when doing:
print p.featuredetail_set.all()
I get and this error:
Django Version: 1.4.3
Exception Type: AttributeError
Exception Value:
'Phone' object has no attribute 'featuredetail_set'
What's wrong?
You can't use a class name as a parameter to select_related. The parameter must be a string of your model field.
In your case, it should be 'features__feature'
Another problem is that select_related cannot follow many-to-many relationship. That's why prefetch_related comes.
Therefore, your queryset would be:
Phone.objects.select_related('brand').prefetch_related('features__feature')
Note that prefetch_related creates an additional query and does the joining in Python.
Your relation is called features, not featuredetail_set.
Note this has nothing to do with select_related, which does nothing in this case (it only works on forward ForeignKeys, anyway).