Django Model Inheritance and limit_choices_to - django

Can anyone tell me how i can limit the choices for the Page model which i inherit from in the following code?
class CaseStudy(Page):
"""
An entry in a fancy picture flow widget for a case study page
"""
image = models.ForeignKey(Image, limit_choices_to={'is_active': True, 'category__code':'RP'})
def __unicode__(self):
return u"%s" % self.title
The django admin is limiting the image choices in a drop down successfully, but i would like to limit a field in the Page model as well (a 'parent page field'), ie:
class Page(models.Model):
parent = models.ForeignKey('self', blank=True, null=True, related_name='children')

I managed to work this out - by overriding the admin model form. I realise this could be tightened up, but thought it might come in use to someone out there. Here's an excerpt from the admin.py
class CaseStudyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(CaseStudyForm, self).__init__(*args, **kwargs)
recent_project_page = Page.objects.get(title="Recent Projects")
parent_widget = self.fields['parent'].widget
choices = []
for key, value in parent_widget.choices:
if key in [recent_project_page.id,]:
choices.append((key, value))
parent_widget.choices = choices
class CaseStudyAdmin(admin.ModelAdmin):
form = CaseStudyForm
admin.site.register(CaseStudy, CaseStudyAdmin)

Related

Update datetimefiled of all related models when model is updated

I have two models (Post and Display). Both have Datetime-auto fields. My problem is that i want to update all display objects related to a post, once a post is updated.
I have read here that you could override one models save method, but all the examples are About updating the model with the foreign key in it and then call the save method of the other model. In my case it's the other way arround. How can i do this ?
class Post(models.Model):
title = models.CharField(max_length=40)
content = models.TextField(max_length=300)
date_posted = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
rooms = models.ManyToManyField(Room, related_name='roomposts', through='Display')
def __str__(self):
return self.title
def get_absolute_url(self):
return "/post/{}/".format(self.pk)
class Display(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
room = models.ForeignKey(Room, on_delete=models.CASCADE)
isdisplayed = models.BooleanField(default=0)
date_posted = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.isdisplayed)
i want to update the date_posted of all related Display-objects once their related post is changed. I do not know if overriding the save-method works here.
in this case you should have a look at django's reverse foreign key documentation
https://docs.djangoproject.com/en/2.2/topics/db/queries/#following-relationships-backward
in your case you can override the save method on your Post model
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
#either: this excutes many sql statments
for display in self.display_set.all():
display.save()
#or faster: this excute only one sql statements,
#but note that this does not call Display.save
self.display_set.all().update(date_posted=self.date_posted)
The name display_set can be changed using the related_name option
in Display, you can change it:
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='displays')
Then, instead of using self.display_set in your code, you can use self.displays
Overriding save method works, but that's not were you should go, imo.
What you need is signals:
#receiver(post_save, sender=Post)
def update_displays_on_post_save(sender, instance, **kwargs):
if kwargs.get('created') is False: # This means you have updated the post
# do smth with instance.display_set
Usually it goes into signals.py.
Also you need to include this in you AppConfig
def ready(self):
from . import signals # noqa

How can make the admin for a ForeignKey('self') ban referring to itself?

I have a model with a forgein key to itself. For example:
class Folder(models.Model):
name = models.CharField()
parent_folder = models.ForeignKey('self', null=True, blank=True, default=None, on_delete=models.CASCADE)
For my purposes, I never want parent_folder to refer to itself, but the default admin interface for this model does allow the user to choose its own instance. How can I stop that from happening?
Edit: If you're trying to do a hierarchical tree layout, like I was, another thing you need to watch out for is circular parent relationships. (For example, A's parent is B, B's parent is C, and C's parent is A.) Avoiding that is not part of this question, but I thought I would mention it as a tip.
I would personally do it at the model level, so if you reuse the model in another form, you would get an error as well:
class Folder(models.Model):
name = models.CharField()
parent_folder = models.ForeignKey('self', null=True, blank=True, default=None, on_delete=models.CASCADE)
def clean(self):
if self.parent_folder == self:
raise ValidationError("A folder can't be its own parent")
If you use this model in a form, use a queryset so the model itself doesn't appear:
class FolderForm(forms.ModelForm):
class Meta:
model = Folder
fields = ('name','parent_folder')
def __init__(self, *args, **kwargs):
super(FolderForm, self).__init__(*args, **kwargs)
if hasattr(self, 'instance') and hasattr(self.instance, 'id'):
self.fields['parent_folder'].queryset = Folder.objects.exclude(id=self.instance.id)
To make sure the user does not select the same instance when filling in the foreign key field, implement a clean_FIELDNAME method in the admin form that rejects that bad value.
In this example, the model is Folder and the foreign key is parent_folder:
from django import forms
from django.contrib import admin
from .models import Folder
class FolderAdminForm(forms.ModelForm):
def clean_parent_folder(self):
if self.cleaned_data["parent_folder"] is None:
return None
if self.cleaned_data["parent_folder"].id == self.instance.id:
raise forms.ValidationError("Invalid parent folder, cannot be itself", code="invalid_parent_folder")
return self.cleaned_data["parent_folder"]
class FolderAdmin(admin.ModelAdmin):
form = FolderAdminForm
admin.site.register(Folder, FolderAdmin)
Edit: Combine my answer with raphv's answer for maximum effectiveness.

Django: why the manytomany choice box only has on side

I have extended the group model, where I added some manytomany fields, and in the admin page, it likes this:
However, what I expected is this:
Here is how I implemented the m2m field:
class MyGroup(ProfileGroup):
mobile = models.CharField(max_length = 15)
email = models.CharField(max_length = 15)
c_annotates = models.ManyToManyField(Annotation, verbose_name=_('annotation'), blank=True, null=True)
c_locations = models.ManyToManyField(Location, verbose_name=_('locations'), blank=True, null=True)
And in the database there is a relational form which contains the pairs of group_id and location_id.
Is there anyone who knows how to do it? Thanks!
EDIT:
I implemented as above, the multiple select box actually shows up, but it cannot save... (Sorry, I was working on a virtual machine and it's offline now, so I have to clip the code from screen)
latest 2017
govt_job_post is model having qualification as ManyToMany field.
class gjobs(admin.ModelAdmin):
filter_horizontal = ('qualification',)
admin.site.register(govt_job_post, gjobs)
Problem solved. It can save the multiple choice field now.
class GroupAdminForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(queryset=User.objects.all(),
widget=FilteredSelectMultiple('Users', False),
required=False)
locations = forms.ModelMultipleChoiceField(queryset=Location.objects.all(),
widget=FilteredSelectMultiple('Location', False),
required=False)
class Meta:
model = Group
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
if instance is not None:
initial = kwargs.get('initial', {})
initial['users'] = instance.user_set.all()
initial['locations'] = instance.c_locations.all()
kwargs['initial'] = initial
super(GroupAdminForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
group = super(GroupAdminForm, self).save(commit=commit)
if commit:
group.user_set = self.cleaned_data['users']
group.locations = self.cleaned_data['locations']
else:
old_save_m2m = self.save_m2m
def new_save_m2m():
old_save_m2m()
group.user_set = self.cleaned_data['users']
group.location_set = self.cleaned_data['locations']
self.save_m2m = new_save_m2m
return group
Either I am overlooking something that makes your situation unusual or you are making it harder than it needs to be. Since you're using the admin, the vast majority of the code necessary to use the admin's more convenient multiselects is already available. All you should need to do is declare your ManyToMany fields, as you have, and then include those fields in your admin class's filter_horizontal attribute. Or filter_vertical if you want the boxes stacked, but your screenshot shows the horizontal case.
This by itself does not require a custom form for your admin.

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.

Adding custom field and updating model problem in django

I need to add a colorpicker to my django model and wrote a custom widget. However when I add this colordfield to my model, django gives this error:
column mediaplanner_ievent.color does not exist
LINE 1: ...nt"."bits", "mediaplanner_ievent"."capture_link", "mediaplan...
My model is :
from mediaplanner.custom_widgets import ColorPickerWidget
class ColorField(models.CharField):
def __init__(self,*args, **kwargs):
kwargs['max_length'] = 10
super(ColorField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
kwargs['widget'] = ColorPickerWidget
return super(ColorField, self).formfield(**kwargs)
class iEvent(models.Model):
name = models.CharField(verbose_name= u"Uygulama Adı", max_length=100, unique=True)
bits = models.CommaSeparatedIntegerField(verbose_name= u"Bitler",max_length=100)
capture_link = models.URLField(verbose_name= u"Capture URL", null=True, blank=True)
color = ColorField(blank=true)
class Meta:
verbose_name = u"red button"
verbose_name_plural = u"red buttonlar"
def __unicode__(self):
return smart_str( "%s"% self.name )
The strange thing is, when I looked my database, there exist colorfield. I don't want to delete the db and load it again. But ofcourse if it's the only solution, then no choice ..
So someone can help me how to solve it?
The field in your database is named colorfield bu the field in your model is named color. You have to change one or the other to make it work again.