Django MTMField: limit_choices_to = other_ForeignKeyField_on_same_model? - python

I've got a couple django models that look like this:
from django.contrib.sites.models import Site
class Photo(models.Model):
title = models.CharField(max_length=100)
site = models.ForeignKey(Site)
file = models.ImageField(upload_to=get_site_profile_path)
def __unicode__(self):
return self.title
class Gallery(models.Model):
name = models.CharField(max_length=40)
site = models.ForeignKey(Site)
photos = models.ManyToManyField(Photo, limit_choices_to = {'site':name} )
def __unicode__(self):
return self.name
I'm having all kinds of fun trying to get the limit_choices_to working on the Gallery model. I only want the Admin to show choices for photos that belong to the same site as this gallery. Is this possible?

Yes. You need to override the form that admin uses for the Gallery model, then limit the queryset of the photos field in that form:
class GalleryAdminForm(django.forms.ModelForm):
class Meta:
model = Gallery
def __init__(self, *args, **kwargs):
super(GalleryAdminForm, self).__init__(*args, **kwargs)
self.fields['segments'].queryset = Photo.objects.filter(site=self.instance.site)
class GalleryAdmin(django.contrib.admin.ModelAdmin):
form = GalleryAdminForm
django.contrib.admin.site.register(Gallery, GalleryAdmin)

I would delete site field on my Photo model and add a ForeignKey to Gallery. I would remove limit_choices_to from photos fields on Gallery model.
Because you are using ForeignKeys to Sites, that means sites don't share galleries and photos. Therefore having those I mentioned above is already useless.
class Photo(models.Model):
title = models.CharField(max_length=100)
gallery = models.ForeignKey(Gallery, related_name='photos')
file = models.ImageField(upload_to=get_site_profile_path)
def __unicode__(self):
return self.title
class Gallery(models.Model):
name = models.CharField(max_length=40)
site = models.ForeignKey(Site)
def __unicode__(self):
return self.name
Once you set the site on a gallery all its photos will inherit this property. And the site will be accessible as photo_instance.gallery.site:
#property
def site(self):
return self.gallery.site
This should work as if you had a site field. But I haven't tested it.
Things change or course, if you decide that a gallery or a photo can appear in multiple sites.

According to the docs, "limit_choices_to has no effect when used on a ManyToManyField with an intermediate table". By my reading, that means it has no effect at all, because ManyToManyFields use intermediate tables...
I haven't tried to make it work in the Admin site, but from your own views, you can create a form and override the queryset used to populate the list of choices:
form.fields["photos"].queryset = request.user.photo_set.all()

Related

Reorganizing ManyToMany models into a different order

I have an setup similar to this.
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
books = models.ManyToManyField(Book)
def __unicode__(self):
return self.title
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author)
def __unicode__(self):
return self.title
Now in my admin panel I can select an author and add Books underneath that specific author. When I print out all the books for each author it prints them in the order that they were added in the admin panel.
Is it possible to modify the order in the admin panel so that when they print, they print in the order of the admin panel?
admin.py
#admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
pass
#admin.register(Book)
class BookAdmin(admin.ModelAdmin):
pass
You can add inside every model a class Meta with ordering atribute, to get items in a certain order.
class Book(models.Model):
...
class Meta:
ordering = ['author']
Maybe it helps you.
as i understand you use TabularInline for books if that's right so you just need to make simple function called get_queryset
def get_queryset(self, request):
return super(BookInlineAdmin, self).get_queryset(request).order_by('YOURFIELD')

Displaying fields other than pk in a Django Form with a ModelChoiceField

I am building a website where users can upload files and can attach uploads to projects that they created beforehand. The upload is done with a django form where the user can specify the title, comments, etc... There is also a dropdown list where the user can choose from the existing projects that he created (list of projects is dependent on user)
As of now the dropdown only shows the (autogenerated) project id which is the pk of the model Project.
I want the dropdown to show the names of the projects and not the project ID which is not very meaningful for the user.
I have already tried
to_field_name='name'
but that didn't work
I have also tried
Project.objects.filter(user=user).values_list('name')
or
Project.objects.filter(user=user).values('name')
the last two options show the project name in {'projectname} but when I select them and submit the form the error "Select a valid choice. That choice is not one of the available choices."
This is my code:
models.py
class Upload(models.Model):
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
upload_date = models.DateTimeField(default=timezone.now)
comments = models.CharField(max_length=10000, null=True)
title = models.CharField(max_length=10000, null=True)
project = models.CharField(max_length=99, default='--None--')
forms.py
class UploadForm(ModelForm):
project = ModelChoiceField(label='Select Project', queryset=Project.objects.all(), to_field_name='name',
empty_label='--Select Project--')
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(UploadForm, self).__init__(*args, **kwargs)
if user is not None:
self.fields['project'].queryset = Project.objects.filter(user=user)
class Meta:
model = Upload
fields = ['title', 'project', 'upload_date', 'comments']
According to docs
The str() method of the model will be called to generate string representations of the objects for use in the field’s choices. To provide customized representations, subclass ModelChoiceField and override label_from_instance. This method will receive a model object and should return a string suitable for representing it.
https://docs.djangoproject.com/en/2.2/ref/forms/fields/#modelchoicefield
so you should define __str__() method for Project model e.g.
def __str__(self):
return self.name

Django admin - OneToOneField inline throws "has no ForeignKey" exception

I have a very simple app which at the moment declares two models: one is called "Content" and simply holds content data and the other is "Page" which includes "Content" as a OneToOneField.
The reason I've done this is so that I can have "Page" as an actual concrete class that I use and when other models in other modules I'm planning need page data, they can simply include "Content" as a OneToOneField. I've done it this way to avoid inheritance and use composition instead.
models.py:
from django.db import models
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
class Page(models.Model):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
content = models.OneToOneField(Content)
def __str__(self):
return self.content.title
admin.py:
from django.contrib import admin
from content.models import Page, Content
class ContentInline(admin.TabularInline):
model = Content
fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')
class PageAdmin(admin.ModelAdmin):
fields = ('slug',)
inlines = [ContentInline]
On the page admin I get this exception:
Exception at /admin/content/page/add/
<class 'content.models.Content'> has no ForeignKey to <class 'content.models.Page'>
What is says of course is correct, but I cannot seem to find a way of doing what I want, which is to include an inline of the non-defining side of a relationship. I don't want to declare the relationship on "Content" as then I'd have to define every single relationship to it inside that class which would introduce dependencies to other modules, which in my opinion it should know nothing about.
Using Django 1.6 on Python 3.3.
Edit: As indicated in the comments, I've decided to use inheritance. My initial concern about this was that I wanted the flexibility to be able to compose classes from multiple other classes. However, since the Django ORM does support multiple inheritance and if I'd realised that method was called "mixins" (new to Python) I would have got somewhere a lot sooner.
Example mixins with models:
from django.db import models
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
def __str__(self):
return self.title
class Meta:
abstract = True
class Data(models.Model):
data_name = models.CharField(max_length=200)
class Meta:
abstract = True
class Page(Content, Data):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
And then I can just use it as one model in admin.py.
Another solution is moving the OneToOneField from Content to Page
class Content(models.Model):
"""Basic page data which can be used by other modules"""
title = models.CharField(max_length=200)
html_title = models.CharField(max_length=200)
meta_desc = models.CharField(max_length=200)
keywords = models.CharField(max_length=200)
content = models.TextField()
page = models.OneToOneField(Page, primary_key=True, related_name="content")
class Page(models.Model):
"""Concrete implementation of a basic page managed by the admin"""
slug = models.SlugField()
def __str__(self):
return self.content.title
You can still do page.content and the inline form will work out of the box
EDIT:
One cons of that approach is that it will allow the user to create a page without assigning any content to it (in which case page.content will crash)
Its very easy to overcome this issue by creating custom form
class ContentAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
kwargs["empty_permitted"] = False
super(ContentAdminForm, self).__init__(*args, **kwargs)
Then in the admin page
class ContentInline(admin.TabularInline):
model = Content
form = ContentAdminForm
fields = ('title', 'html_title', 'meta_desc', 'keywords', 'content')
If you don't want to change your models at all, there's a django module to display the non-defining side inline: django_reverse_admin
You'll need to add django_reverse_admin to your requirements.txt:
-e git+https://github.com/anziem/django_reverse_admin.git#egg=django_reverse_admin
Then import it:
admin.py
from django.contrib import admin
from django_reverse_admin import ReverseModelAdmin
from content.models import Page, Content
# don't need to define an inline anymore for Content
class PageAdmin(ReverseModelAdmin):
fields = ('slug',)
inline_reverse = ['content']
inline_type = 'tabular' # or could be 'stacked'

Don't want show all items of a model object in django

class TaskManager(models.Manager):
def get_query_set(self):
return super(TaskManager, self).get_query_set().filter(Owner='jim')
class Task(models.Model):
Name = models.CharField('Title', max_length=200)
Notes = models.TextField('Description',max_length=2000, null=True)
project = models.ForeignKey(Project,null=True, blank=True)
Owner = models.CharField(max_length=100, choices=owner_set)
objects = TaskManager()
def __unicode__(self):
return self.Name
I have two models in my models.py, every Task has a project. But when i enter the Task page, it will list all the task by default. So i want it to list only the current user's Task by default. It is that the Task.Owner=current user.
Who can help me with this, thanks!
I have searched from the net and then get the solution:
I update the code i pasted just now above.
Thanks for all.
Why not do this!
Task.objects.filter(owner = current_user)
Update:
1) If you want to filter in you own custom template add this to your view code
2) If you are trying to customize the admin site do this,
from django.contrib import admin
from models import MyModel
class TaskAdmin(admin.ModelAdmin):
def queryset(self, request):
return super(TaskAdmin, self).queryset(request).filter(owner = request.user)
admin.site.register(Task, TaskAdmin)
In the ModelAdmin class you should add:
class TaskAdmin(...):
def queryset(self, request):
qs = super(TaskAdmin, self).queryset(request)
return qs.filter(Owner=request.user)
This will override the default queryset
more here:
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.queryset
I just want to modify the default filter of a model, make the model list not all items without the modelAdmin, because the modelAdmin just provide a filter which need manual click, i want to filter the model by default automaticlly

admin template for manytomany

I have a manytomany relationship between publication and pathology. Each publication can have many pathologies. When a publication appears in the admin template, I need to be able to see the many pathologies associated with that publication. Here is the model statement:
class Pathology(models.Model):
pathology = models.CharField(max_length=100)
def __unicode__(self):
return self.pathology
class Meta:
ordering = ["pathology"]
class Publication(models.Model):
pubtitle = models.TextField()
pathology = models.ManyToManyField(Pathology)
def __unicode__(self):
return self.pubtitle
class Meta:
ordering = ["pubtitle"]
Here is the admin.py. I have tried variations of the following, but always
get an error saying either publication or pathology doesn't have a foreign key
associated.
from myprograms.cpssite.models import Pathology
class PathologyAdmin(admin.ModelAdmin):
# ...
list_display = ('pathology', 'id')
admin.site.register(Pathology, PathologyAdmin)
class PathologyInline(admin.TabularInline):
#...
model = Pathology
extra = 3
class PublicationAdmin(admin.ModelAdmin):
# ...
ordering = ('pubtitle', 'year')
inlines = [PathologyInline]
admin.site.register(Publication,PublicationAdmin)
Thanks for any help.
Unless you are using a intermediate table as documented here http://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-intermediary-models, I don't think you need to create an Inline class. Try removing the line includes=[PathologyInline] and see what happens.
I realize now that Django is great for the administration (data entry) of a website, simple searching and template inheritance, but Django and Python are not very good for complex web applications, where data is moved back and forth between a database and an html template. I have decided to combine Django and PHP, hopefully, applying the strengths of both. Thanks for you help!
That looks more like a one-to-many relationship to me, tho I'm somewhat unclear on what exactly Pathologies are. Also, so far as I understand, Inlines don't work on manytomany. That should work if you flip the order of the models, remove the manytomany and add a ForeignKey field to Publication in Pathology.
class Publication(models.Model):
pubtitle = models.TextField()
def __unicode__(self):
return self.pubtitle
class Meta:
ordering = ["pubtitle"]
class Pathology(models.Model):
pathology = models.CharField(max_length=100)
publication = models.ForeignKey(Publication)
def __unicode__(self):
return self.pathology
class Meta:
ordering = ["pathology"]

Categories