Django ORM m2m limit to 1 - django

# models.py
class Gallery(models.Model):
images = models.ManyToManyField(Image, null=True, blank=True)
class Image(models.Model):
image = models.ImageField()
# views.py
class GalleryIndex(ListView):
model = Gallery
I need to get thumbnail for every gallery, which is it's very first/last/whatever image.
How can I LIMIT image by 1 for any gallery as a custom attribute (to not override Gallery.images) without calling second SQL query?

A many-to-many acts as a descriptor for a normal queryset, so you can do my_gallery.images.all()[0] to limit the query to 1.

I don't think I understand correctly what you want to do, but doesn't below code work for you?
class Gallery(models.Model):
images = models.ManyToManyField(Image, null=True, blank=True)
def get_thumb(self):
return self.images.all()[0]
Or maybe other concept:
class Gallery(models.Model):
images = models.ManyToManyField(Image, null=True, blank=True)
thumbnail = models.ImageField()
def save(self, *args, **kwargs):
self.thumbnail = self.images.all()[0].image
(some exception catching here needed though)

I should've read docs better. Standard QuerySet API can't handle such cases efficiently (annotate() generates GROUP BY clause for each of parent fields which is slow) so I come with extra() method and raw subquery.
class GalleryIndex(ListView):
queryset = Gallery.objects.extra(select={'thumb':
"""
SELECT "image"
FROM "app_image", "app_gallery_images"
WHERE (
"app_gallery"."id" = "app_gallery_images"."gallery_id"
AND "app_gallery_images"."image_id" = "app_image"."id"
)
AND "app_image"."image" IS NOT NULL
LIMIT 1
"""
})
This queryset do exactly what I wanted, since SorlImageField (and ImageField) needs only filename to represent thumbnail in templates.

Related

django manytomany model relationship crashing admin on object create

I have an Event object in my postgres db, and created a new Collection object to group events by theme via a ManyToMany field relationship:
class Collection(models.Model):
event = models.ManyToManyField('Event', related_name='collections')
name = models.CharField(blank=True, max_length=280)
slug = AutoSlugField(populate_from='name')
image = models.ImageField(upload_to='collection_images/', blank=True)
description = models.TextField(blank=True, max_length=1000)
theme = models.ManyToManyField('common.Tag', related_name='themes')
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=False)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('collection', args=[self.slug])
def clean(self):
# because of the way db saves M2M relations, collection doesn't have a
# type at this time yet, so image inheritance is
# called from the signal which is triggered when M2M is created
# (that means if an image is later deleted, it won't inherit a new
# one when collection is saved)
if self.image:
validate_hero_image(self.image, 'image')
def save(self, *args, **kwargs):
try:
self.full_clean()
except ValidationError as e:
log.error('Collection validation error (name = %s): %s' % (self.name, e))
return super(Collection, self).save(*args, **kwargs)
in my admin, I'm defining and registering CollectionAdmin like this:
class CollectionAdmin(admin.ModelAdmin):
model = Collection
verbose_name = 'Collection'
list_display = ( 'name', )
however, if I go into admin and attempt to create a Collection "GET /admin/app/collection/add/" 200, the request frequently times out and the query load on my database from the Event M2M relationship seems quite heavy from logging. For reference currently the db has ~100,000 events. are there better ways to (re)structure my admin fields so I can select specific events (by name or id) to add to a Collection without effectively requesting a QuerySet of all events when that view is loaded (or creating them in db via shell)? thanks
There are multiple ways to accomplish this. For example, you could override the form fields the admin uses and specify another widget to use, like NumberInput.
You could also add your event model field to the raw_id_fields attribute of ModelAdmin. By doing so, Django won't try to create a fully populated select input but will offer you a way to search for events manually if needed:
class CollectionAdmin(admin.ModelAdmin):
model = Collection
verbose_name = 'Collection'
list_display = ('name', )
raw_id_fields = ('event', )

Why won't serialize capture annotate fields?

I had no idea adding data to a queryset would be so hard. It's like, if it didn't come directly from the db then it might as well not exist. Even when I annotate, the new fields are 2nd class citizens and aren't always available.
Why won't serialize capture my annotate fields?
Model
class Parc(models.Model):
# Regular Django fields corresponding to the attributes in the
# world borders shapefile.
prop_id = models.IntegerField(unique=True) # OBJECTID: Integer (10.0)
shp_id = models.IntegerField()
# GeoDjango-specific: a geometry field (MultiPolygonField)
mpoly = models.MultiPolygonField(srid=2277)
sale_price = models.DecimalField(max_digits=8, decimal_places=2, null=True)
floorplan_area = models.DecimalField(max_digits=8, decimal_places=2, null=True)
price_per_area = models.DecimalField(max_digits=8, decimal_places=2, null=True)
nbhd = models.CharField(max_length=200, null=True)
# Returns the string representation of the model.
def __str__(self): # __unicode__ on Python 2
return str(self.shp_id)
Query:
parcels = Parc.objects\
.filter(prop_id__in=attrList)\
.order_by('prop_id') \
.annotate(avg_price=Avg('sale_price'),
perc_90_price=RawAnnotation('percentile_disc(%s) WITHIN GROUP (ORDER BY sale_price)', (0.9,)),
)
geojson = serialize('geojson', parcels)
When I print geojson it has no key/values for avg_price or perc_90_price. At this point, I'm leaning towards creating a dummy field and then populating it with the my customer calculations after I retrieve the queryset but I'm open to ideas.
Helper class
class RawAnnotation(RawSQL):
"""
RawSQL also aggregates the SQL to the `group by` clause which defeats the purpose of adding it to an Annotation.
"""
def get_group_by_cols(self):
return []
I use annotations with Django Rest Framework and the Serializers in that library.
In particular, the serializer method allows you to access the query set. You can do something like this.
class SomeSerializer(serializers.ModelSerializer):
avg_price = serializers.SerializerMethodField()
def get_avg_price(self, obj):
try:
return obj.avg_price
except:
return None
As mentioned by Carl Kroeger Ihl, you can also use:
class SomeSerializer(serializers.ModelSerializer):
avg_price = serializers.IntegerField(allow_null=True)
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

Django - uploading several images at the time

I need your help in such question:
This is my models.py:
class Location(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
name = models.CharField(max_length=100, verbose_name="Локация", default=u'')
photos = models.ImageField(upload_to='photos', null=True)
This is my forms.py:
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ['name', 'photos']
This is my views.py:
class AddLocationPageView(FormView):
template_name = 'add_location.html'
form_class = LocationForm
def form_valid(self, form):
form.save()
return super(AddLocationPageView, self).form_valid(form)
I need to have possibility of uploading several photos at the time.
How can I do that?
Thanks!
Since I'm assuming you're using a relational database and your model field name is 'photos' you'll want more than one photo per location.
You can do something like:
class Image(models.Model):
full_size = models.ImageField()
thumbnail = models.ImageField()
location = models.ForeignKey('app_label.Location', related_name='photos')
and remove the image field from the Location model.
To upload multiple photos, you'll want to use a formset. Depending on the interface you want, you'll probably want to use a model formset so the photos get their location_id set properly.
With your form, all you need to do is use the formset_factory function that you can call in your view (probably in get_context_data).
Handling the formset in your view involves some logic wrangling but there is a project called django-extra-views that implements the form with a formset logic, again, depending on the interface you're going for here.
If you want to just add photos to a pre-existing location, that is much simpler: just include the model_formset with a location object.

Django 1.5 ModelForm like admin in view with images and foreign key

I have the following models:
class Quiver(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
is_default = models.BooleanField(default=False)
type = models.CharField(max_length=1, choices=QUIVER_TYPES)
category = models.CharField(max_length=255, choices=QUIVER_CATEGORIES)
def __unicode__(self):
return u'[%s] %s %s quiver' % (
self.user.username,
self.get_type_display(),
self.get_category_display())
class Image(models.Model):
photo = models.ImageField(upload_to=get_upload_file_path)
is_cover = models.BooleanField(default=False)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
def save(self, *args, **kwargs):
try:
this = Image.objects.get(pk=self.pk)
if this.photo != self.photo:
this.photo.delete(save=False)
except Image.DoesNotExist:
pass
super(Image, self).save(*args, **kwargs)
class Surfboard(models.Model):
quiver = models.ForeignKey(Quiver)
brand = models.CharField(max_length=255)
model = models.CharField(max_length=255)
length = models.CharField(max_length=255)
width = models.CharField(max_length=255, blank=True)
thickness = models.CharField(max_length=255, blank=True)
volume = models.CharField(max_length=255, blank=True)
images = generic.GenericRelation(Image)
def __unicode__(self):
return u'%s %s %s' % (self.length, self.brand, self.model)
def get_cover_image(self):
"Returns the cover image from the images uploaded or a default one"
for image in self.images.all():
if image.is_cover:
return image
return None
I'd like to be able to have the same form I have in the admin in my frontend view /surfboard/add:
As a new Django fan and user, I started to create the form from scratch. Not being able to do what I want with including the foreign key "quiver" as a dropdown list, I found in the doc the ModelForm, and decided to use it, so here what I got:
class SurfboardForm(ModelForm):
class Meta:
model = Surfboard
In my view, it looks like this and it's already a good start:
So now, I wanted to have a way to add pictures at the same time, and they are linked to a surfboard via a Generic Relation. Here I don't find the way to do a implementation like in the admin, and get frustrated. Any tips to do so?
Thanks!
What you seek is called an inline formset - see the docs for more.
It's also handy that you can render a formset quickly with {{ formset.as_p }}, but you'll need to write some JavaScript (or use the JavaScript that's used in the Django admin) to handle adding and removing forms.

How can I let a user to post several images to one model?

I have one listing model :
class Listing(models.Model):
owner = models.ForeignKey(User, verbose_name=_('offerer'))
title = models.CharField(_('Title'), max_length=255)
slug = models.CharField(editable=False, max_length=255)
price = models.PositiveIntegerField(_("Price"), null=True, blank=True)
description = models.TextField(_('Description'))
time = models.DateTimeField(_('Created time'),
default = datetime.now,
editable = False
)
Then I have one ListingImage, which holds the pictures of the listing:
from photologue.models import ImageModel
class ListingImage(ImageModel):
pictures = models.ForeignKey(Listing, related_name="images")
forms.py
class ListingForm(forms.ModelForm):
class Meta:
model = Listing
exclude = ('owner',)
def __init__(self, *args, **kwargs):
super(ListingForm, self).__init__(*args, **kwargs)
Why in the upload page , there is no field to upload a picture??
ListingImage has a ForeignKey to Listing, so a ModelForm for Listing has nothing to do with ListingImage.
You shouldn't be expecting a ModelForm for the Listing model to show you anything but the Listing model. ListingImage is a reverse relationship to the Listing model.
If this was a ModelAdmin, you'd get the admin site to show you these reverse relationships by defining inlines:
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin
Since it doesn't look like you're talking about the admin panel, you're looking at InlineModelFormsets: http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#using-an-inline-formset-in-a-view
Also, you could show us your views so that we can see the whole picture.