django - Get a set of objects from different models field names - django

Please have a look at my models.
class BackgroundImage(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
class ProfilePicture(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
class Album(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
class Meta:
ordering = ['-pub_date']
verbose_name_plural = ('Albums')
def __unicode__(self):
return self.name
class Photo(models.Model):
user = models.ForeignKey(User)
album = models.ForeignKey(Album, default=3)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
How do I get all the images of Photo, ProfilePicture and BackgroundImage from their image field in one set. And then filter them by -pub_date to display in the template? Please help me out. Will be much much appreciated! Thank you.
Edit
N.B: I need ProfilePicture and BackgroundImage to work with the UserProfile like this:
from django.db import models
from django.contrib.auth.models import User
from profile_picture.models import ProfilePicture
from background_image.models import BackgroundImage
class UserProfile(models.Model):
user = models.OneToOneField(User)
permanent_address = models.TextField()
temporary_address = models.TextField()
profile_pic = models.ForeignKey(ProfilePicture)
background_pic = models.ForeignKey(BackgroundImage)
def __unicode__(self):
return self.user.username
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

There is an InheritanceManager provided as part of django-model-utils which allows you to do this, see docs.
To install in Linux / Mac:
sudo pip install django-model-utils
Annoyingly, installing using easy_install or pip on windows is not quite as straight forward, see: How do I install Python packages on Windows?. A quick and dirty method is to download the django-model-util/ directory from here into the top directory of your django project, this is handy if you intend to copy the entire project across for deployment to a production webserver.
In order to use the InheritanceManager, the models need to be refactored slightly:
from django.db import models
from django.contrib.auth.models import User
from datetime import datetime
from model_utils.managers import InheritanceManager
get_upload_file_name = 'images/' # I added this to debug models
class BaseImage(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to=get_upload_file_name)
caption = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
objects = InheritanceManager()
class BackgroundImage(BaseImage):
pass
class ProfilePicture(BaseImage):
pass
class Album(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=200)
pub_date = models.DateTimeField(default=datetime.now)
class Meta:
ordering = ['-pub_date']
verbose_name_plural = ('Albums')
def __unicode__(self):
return self.name
class Photo(BaseImage):
album = models.ForeignKey(Album, default=3)
All the Image models now inherit from a common super class which creates an instance of the InheritanceManager. I've also moved up all the duplicated attributes into the superclass, but this isn't strictly necessary, using InheritanceManager means that any attributes which are not present in BaseImage can still be accessed in the template.
To retrieve a list ordered by -pubdate:
BaseImage.objects.select_subclasses().order_by("-pub_date")
To use in a view:
def recentImages(request):
r = BaseImage.objects.select_subclasses().order_by("-pub_date")[:20]
return render_to_response("recentImages.html", { "imageList" : r })
To use in a template:
{% for photo in imageList %}
<img src="{{ photo.image.url }}" />
{% endfor %}
Is this something like what you are looking for?
Edit
The following code will still work fine, with the new models:
class UserProfile(models.Model):
user = models.OneToOneField(User)
permanent_address = models.TextField()
temporary_address = models.TextField()
profile_pic = models.ForeignKey(ProfilePicture)
background_pic = models.ForeignKey(BackgroundImage)
Just make sure the names of the last two models in the ForeignKey relationship are correct!

Related

Django - Linking Models

I am using django and I want to link two models. The first model is comment and the second model is image. I want to have multiple images for one comment and an image should be linked with only one comment.
Comment model has its fields and image model looks like this:
class Image(models.Model):
image = models.ImageField(upload_to=f'{hash(id)}/', null=True, blank=True)
def __str__(self):
return self.image.name
And this is the model that I used to link comment and image:
class CommentImage(models.Model):
comment = models.OneToOneField(Comment, models.CASCADE, null=True)
image = models.ForeignKey(Image, models.CASCADE, null=True)
class Meta:
ordering = ["-id"]
verbose_name = _("Image")
verbose_name_plural = _("Images")
def __str__(self):
return self.image.image.name
Here is the admin panel of django:
enter image description here
As you can see I could be able add only one image and there is no button as well to add multiple image. What should I change to be able to add multiple images?
I have tried using ManytoManyField and removing comment field from CommentImage but it did not work.
I think you are overcomplicating things. Why not just add a text field to your ImageComment:
class Image(models.Model):
def upload_to(self, filename):
return f'{hash(self)}/{filename}'
image = models.ImageField(upload_to=upload_to, null=True, blank=True)
comment = models.ForeignKey(
'ImageComment', on_delete=models.SET_NULL, null=True
)
def __str__(self):
return self.image.name
class CommentImage(models.Model):
comment = models.TextField()
Or in case an Image can have multiple ImageComments as well, use a ManyToManyField:
class Image(models.Model):
def upload_to(self, filename):
return f'{hash(self)}/{filename}'
image = models.ImageField(upload_to=upload_to, null=True, blank=True)
comments = models.ManyToManyField(
'ImageComment'
)
def __str__(self):
return self.image.name
class CommentImage(models.Model):
comment = models.TextField()
You can even add an InlineModelAdmin to make editing comments at the same location as the image possible:
from django.contrib import admin
class ImageCommentInline(admin.TabularInline):
model = ImageComment
#admin.site.register(Image)
class ImageAdmin(admin.ModelAdmin):
inlines = [
ImageCommentInline,
]
You can try using this via manytomanyfields:
class CommentImage(models.Model):
comment = models.ForeignKey(Comment, models.CASCADE)
image = models.ForeignKey(Image, models.CASCADE)
class Comment(models.Model):
# other fields here
images = models.ManyToManyField(Image, through='CommentImage', related_name='comments')

Django model select between two columns/fields?

I have a Slider module that i want to include items from movies_movie and shows_show table. An item can either be a show or movie. How do i make user select between movie and show? Currently i have columns for movie and show but how do i force user to select between the two?
also title_en is a column in movie or tv show tables. So the title of the movie/show selected should display in row after save.
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True)
def __str__(self):
return self.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
Also if a show is selected and a movie isn't, how do i know title_en will be taken from show and not movie?
I think you can do something like this:
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
class Slider_items(models.Model):
order = models.IntegerField(max_length=3, blank=True)
# don't forget to add null=True to both fields
movie = models.ForeignKey('movies.movie', on_delete=models.CASCADE, blank=True, null=True)
show = models.ForeignKey('shows.show', on_delete=models.CASCADE, blank=True, null=True)
# see docs, https://docs.djangoproject.com/en/3.2/ref/models/instances/#django.db.models.Model.clean
def clean(self):
if self.movie and self.show:
raise ValidationError({'movie': _('You can't select both types at the same time')})
elif not self.movie and not self.show:
raise ValidationError({'movie': _('You must select one type')})
def __str__(self):
return self.movie.title_en if self.movie else self.show.title_en
class Meta:
verbose_name = "Slider Items Module"
verbose_name_plural = "Slider Item Module"
You may consider using django contenttypes.
Imagine in the future, you have not just Movie, Show, but have new Class such as Book, Podcase, it might not be a good idea to keep adding new foreignkey to your Slider Model.
I have not used contenttype before, so I am referencing this SO answer.
(using python 3.6, django 3.2)
models.py
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
class Movie(models.Model):
title = models.CharField(max_length=50)
director = models.CharField(max_length=50)
class Show(models.Model):
title = models.CharField(max_length=50)
date = models.DateField()
class Slider(models.Model):
order = models.IntegerField(max_length=3, blank=True)
choices = models.Q(model='movie') | models.Q(model='show')
selection_type = models.ForeignKey(
ContentType, limit_choices_to=choices,
on_delete=models.CASCADE)
selection_id = models.PositiveIntegerField()
selection = GenericForeignKey('selection_type', 'selection_id')
def __str__(self):
return self.selection.title
admin.py
#admin.register(Slider)
class SliderAdmin(admin.ModelAdmin):
pass
at django shell, the following is valid.
movie = Movie.objects.create(title='movie 1', director='ben')
show = Show.objects.create(title='show 1', date='2021-01-01')
s1 = Slider.objects.create(selection=movie, order=1)
s2 = Slider.objects.create(selection=show, order=2)
However, using limit_choices_to only restrict the choices in admin page, and there is no constraint at database level. i.e. the following are actually legal.
place = Place.objects.create(name='home')
s3 = Slider.objects.create(selection=s3, order=3)
I have not found a fix for this issue yet. Maybe doing some validation in save method is a way (see the comments under this).

Django Admin escaping text

I recently upgraded Django from 1.3 to 1.8.18 and been having issues with links custom made to pre-fill forms in Django admin. For example, I have the following link:
/admin/miscellaneous/whatsnew/add/?title=...%20competition%20results%20uploaded&pub_date=21-04-2017&body=&link=
When executed the pre-filled data in the form looks like:
Where it should look like this:
When testing directly from the URL bar in Safari it changes to this after pressing enter:
https://flyball.org.au/admin/miscellaneous/whatsnew/add/?title=...%2520competition%2520results%2520uploaded&pub_date=21-04-2017&body=&link=
models.py
class WhatsNew(models.Model):
title = models.CharField(max_length=100,help_text='Title, MAX 100 characters.')
body = models.TextField()
pub_date = models.DateField()
message_expiry = models.DateField(default=datetime.date.today() + relativedelta(years=1))
link = models.URLField(blank=True, null=True)
class Meta:
ordering = ['-pub_date']
verbose_name_plural = "Whats New?"
def __unicode__(self):
return self.title
admin.py
import models
from django.contrib import admin
class WhatsNewAdmin(admin.ModelAdmin):
list_display = ('title','pub_date','message_expiry','link','body')
admin.site.register(models.WhatsNew, WhatsNewAdmin)
What can I do to resolve this?
So, I'm not sure how to do it on the ModelAdmin, but you can create custom setters on your model to handle this situation. Here's how I would go about escaping the URL encoded strings:
import urllib
class WhatsNew(models.Model):
# Field with custom setter
_title = models.CharField(max_length=100,
help_text='Title, MAX 100 characters.',
db_column='title')
body = models.TextField()
pub_date = models.DateField()
message_expiry = models.DateField(default=datetime.date.today() + relativedelta(years=1))
link = models.URLField(blank=True, null=True)
# Custom getter and setter
#property
def title(self):
return self._title
#title.setter
def title(self, value):
self._title = urllib.unquote(value)
class Meta:
ordering = ['-pub_date']
verbose_name_plural = "Whats New?"
def __unicode__(self):
return self._title
Use + instead of %20 for space and it works.
Your link should be something like:
/admin/miscellaneous/whatsnew/add/?title=...+competition+results+uploaded&pub_date=21-04-2017&body=&link=

Django Tabular in Line example

I really need somebody to explain/show me how I can achieve a TabularInline display in the django admin console of my example. Could somebody help me out?
My models are as follows:
from django.db import models
class Player(models.Model):
player_id = models.IntegerField(primary_key=True)
team = models.ForeignKey(Team)
player_name = models.CharField(max_length=140)
position = models.CharField(max_length=10)
def __str__(self):
return '%s' % (self.player_name)
class MatchdayStats(models.Model):
MATCHDAY_STATS_ID = models.AutoField(primary_key=True)
appeared = models.BooleanField(default=False)
goal = models.IntegerField(default=0)
minutes_under_60 = models.BooleanField(default=False)
minutes_60 = models.BooleanField(default=False)
assist = models.IntegerField(default=0)
def __str__(self):
return '%s' % (self.MATCHDAY_STATS_ID)
class PlayerGameweekStats(models.Model):
PLAYER_GAMEWEEK_ALLSTATS_ID = models.AutoField(primary_key=True)
player = models.ForeignKey(Player)
gameweek = models.ForeignKey('fixturesresults.Gameweek')
matchday_stats = models.ForeignKey(MatchdayStats)
def __str__(self):
return '%s (gw=%s,msid=%s)' % (self.player.player_name,self.gameweek.GAMEWEEK_ID,self.matchday_stats.MATCHDAY_STATS_ID)
I would like there to be a tabular display for the PlayerGameweekStats model, where you can enter MatchdayStats fields for each player.
The admin code below causes a Foreign Key error <class 'playerteamstats.models.MatchdayStats'> has no ForeignKey to <class 'playerteamstats.models.PlayerGameweekStats'>
class StatsInLine(admin.TabularInline):
model = MatchdayStats
class PlayerGameweekStatsAdmin(admin.ModelAdmin):
list_display = ('player', 'gameweek')
exclude = ('gameweek')
inlines = [
StatsInLine,
]
admin.site.register(PlayerGameweekStats, PlayerGameweekStatsAdmin)
To build TabularInline models need to be connected with ForeignKey.
From Django docs example:
models.py:
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100)
admin.py:
from django.contrib import admin
class BookInline(admin.TabularInline):
model = Book
class AuthorAdmin(admin.ModelAdmin):
inlines = [
BookInline,
]
In you case you need to have ForeignKey to PlayerGameweekStats in MatchdayStats.

How to include rows of data in a single django model entry?

I am working on multiple django sites and have been limited in making my project look nice for clients.
For example in the same app I have two models images and image galleries. It would be so much nicer to just have an admin entry for galleries and in that a table of images.
That's exactly what InlineModelAdmin is for. Taken a models.py like this:
class Gallery(models.Model):
name = models.CharField(max_length=100)
class Image(models.Model):
image = models.ImageField()
gallery = models.ForeignKey(Gallery)
You create an admin.py like this and only register an admin class for the Gallery:
class ImageInline(admin.TabularInline):
model = Image
class GalleryAdmin(admin.ModelAdmin):
inlines = [ImageInline]
admin.site.register(Gallery, GalleryAdmin)
This is my solution thanks to Dirk's help.
from django.db import models
PHOTO_PATH = 'media_gallery'
class Gallerys(models.Model):
title = models.CharField(max_length=30, help_text='Title of the image maximum 30 characters.')
slug = models.SlugField(unique_for_date='date', help_text='This is automatic, used in the URL.')
date = models.DateTimeField()
class Meta:
verbose_name_plural = "Image Galleries"
ordering = ('-date',)
def __unicode__(self):
return self.title
class Images(models.Model):
title = models.CharField(max_length=30, help_text='Title of the image maximum 30 characters.')
content = models.FileField(upload_to=PHOTO_PATH,blank=False, help_text='Ensure the image size is small and it\'s aspect ratio is 16:9.')
gallery = models.ForeignKey(Gallerys)
date = models.DateTimeField()
class Meta:
verbose_name_plural = "Images"
ordering = ('-date',)
def __unicode__(self):
return self.title
import models
from django.contrib import admin
class ImageInline(admin.TabularInline):
model = Images
class GallerysAdmin(admin.ModelAdmin):
list_display = ('title', 'date', 'slug')
inlines = [ImageInline]
admin.site.register(models.Gallerys,GallerysAdmin)