I'm trying to add a comments component to a bug tracking application using django. I have a text field for comments and a by field--auto-propagated by user id.
I want the comments text field to become read-only after someone saves a comment. I've tried doing this several ways. The best way I have come up with so far is to pass my Comment model into a ModelForm and then use form widget attributes to convert my field to read only.
models.py
class CommentForm(ModelForm):
class Meta:
model = Comment
exclude = ('ticket', 'submitted_date', 'modified_date')
def __init__(self, *args, **kwargs):
super(CommentForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.id:
self.fields['comments'].widget.attrs['readonly'] = True
class Comment(models.Model):
ticket = models.ForeignKey(Ticket)
by = models.ForeignKey(User, null=True, blank=True, related_name="by")
comments = models.TextField(null=True, blank=True)
submitted_date = models.DateField(auto_now_add=True)
modified_date = models.DateField(auto_now=True)
class Admin:
list_display = ('comments', 'by',
'submitted_date', 'modified_date')
list_filter = ('submitted_date', 'by',)
search_fields = ('comments', 'by',)
My Comment model is associated with my Ticket model in the bug tracking program. I connect the comments to the tickets by placing the comments in an inline in admin.py. The problem now becomes: how do I pass the ModelForm into a TabularInline? TabularInline demands a defined model. However, once I've passed a model into my inline, passing a model form becomes moot.
admin.py
class CommentInline(admin.TabularInline):
model = Comment
form = CommentForm()
search_fields = ['by', ]
list_filter = ['by', ]
fields = ('comments', 'by')
readonly_fields=('by',)
extra = 1
Does anyone know how to pass a ModelForm into a TabularInline without having a regular Model's fields override the ModelForm? Thanks in advance!
Don't instantiate the form in the TabularInline subclass:
form = CommentForm
Related
The current problem is that my form shows the logged in user all Portfolios ever created. The form should only show portfolios that the logged-in user created.
Something like this:
associated_portfolios manytomany field = ...objects.filter(user=user_id)
I'm not sure if this should be implemented in the forms.py or views.py and if so how. I've been going through the django documentation and found 'formfield_for_manytomany' but not sure if this is only meant for admin.
Models.py
class Portfolio(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=20)
description = models.CharField(max_length=250, blank=True, null=True)
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50)
body = RichTextUploadingField(blank=True, null=True)
associated_portfolios = models.ManyToManyField(Portfolio, blank=True)
created_on = models.DateField(auto_now_add=True, editable=False)
Views.py
class PostCreate(CreateView):
model = Post
form_class = PostCreateForm
def formfield_for_manytomany(self, db_field, request, **kwargs):
self.fields['associated_portfolios'] = Portfolio.objects.filter(user=self.request.user)
return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs)
forms.py
class PortfolioCreateForm(ModelForm):
class Meta:
model = Portfolio
fields = ['user', 'name', 'description']
class PostCreateForm(ModelForm):
class Meta:
model = Post
fields = ['user', 'title', 'body', 'category', 'associated_portfolios']
Since you're using a ModelForm, the associated_protfolios field will be a ModelMultipleChoiceField [docs]. This field has a queryset attribute [docs]. We want to modify that attribute.
Django's CreateView has a method get_form, which in this case will grab your PostCreateForm. This is a good spot to filter the field's queryset, since we have access to the user:
class PostCreate(CreateView):
model = Post
form_class = PostCreateForm
def get_form(self, *args, **kwargs):
form = super().get_form(*args, **kwargs) # Get the form as usual
user = self.request.user
form.fileds['associated_portfolios'].queryset = Portfolio.objects.filter(user=user)
return form
Did you try this
self.fields['associated_portfolios'] = Post.objects.filter(associated_portfolios__portfolio__user=request.user)
OR
user_posts = Post.objects.filter(user=request.user)
self.fields['associated_portfolios'] = user_posts.associated_portfolios.all()
read more about M2M relationships querying here, because I think your problem may be with it.
Also, I'm not sure about your actual data maybe it's right and it gives a correct result as filtering Portfolio model against current user to get its objects looks right for me, but anyway double check everything again.
And as a final note, add related_name to your model fields so you can use it easily for reverse relations rather than going with Django's default naming, it will be clearer and give a better understanding.
I've got a 'Registration' object in place that users can create on the front end without issue.
It looks like this:
class Registration(models.Model):
person = models.ForeignKey(Person, on_delete=models.PROTECT)
course_detail = models.ForeignKey(CourseDetail, on_delete=models.PROTECT)
camp_shirt = models.ForeignKey(CampShirt, on_delete=models.PROTECT)
comments = models.CharField(max_length=200, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return "%s" % (self.course_detail.course.camp)
When I am in the admin and click on a given Registration - it takes a while to load because there are thousands and thousands of Person objects.
For ease of use - there will never be a time when we would need to edit the 'person' associated with a given registration, so I would like to make the 'person' dropdown only show the selected user in the person queryset when editing from the django admin.
So when I go to http://myapp.com/admin/registration/23/change I want the form to only display the currently selected person as the only option in the dropdown.
My admin model looks like this:
class RegistrationAdmin(admin.ModelAdmin):
list_display = ("person", "course_detail")
class Meta:
# I think this is what I do in order to override the default admin form? Not sure.
form = RegistrationAdminForm
My RegistrationAdminForm looks like this:
class RegistrationAdminForm(forms.ModelForm):
# course_detail, person, camp_shirt, comments
person = forms.ModelChoiceField(queryset=Person.objects.filter(
id=registration.person.id)
)
def __init__(self, registration, *args, **kwargs):
super(RegistrationAdminForm, self).__init__(*args, **kwargs)
self.fields['person'].queryset = Person.objects.filter(
id=registration.person.id
)
class Meta:
model = Registration
fields = '__all__'
Main Question : How do I change the admin form so that a specific queryset is returned for one of the fields in the django admin?
If the person field will never be changed you can add the person field to readonly_fields, a select with all Person objects will not be rendered.
class RegistrationAdmin(admin.ModelAdmin):
list_display = ("person", "course_detail")
readonly_fields = ("person", )
Then you do not need your custom form. FYI when you want to add a custom form to a ModelAdmin you do not put it in Meta, you define it on the form itself
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
I can't seem to work out how to hook into the queryset of a readonly field in Django admin. In particular I want to do this for an inline admin.
# models.py
class Value(models.Model):
name = models.TextField()
class AnotherModel(models.Model):
values = models.ManyToManyField(Value)
class Model(models.Model):
another_model = models.ForeignKey(AnotherModel)
# admin.py
class AnotherModelInline(admin.TabularInline):
# How do I order values by 'name'?
readonly_fields = ('values',)
class ModelAdmin(admin.ModelAdmin):
inlines = (AnotherModelInline,)
Note that this could probably be done by overriding the form and then setting the widget to disabled, but that's a bit of a hack and doesn't look nice (I don't want greyed out multi-select, but a comma-separated list of words.
You can set an ordering metadata in the Values model:
class Value(models.Model):
name = models.TextField()
class Meta:
ordering = ['name']
I'm trying to limit Django Admin choices of a ForeignKey using limit_choices_to, but I can't figure out how to do it properly.
This code does what I want if the category id is 16, but I can't figure out how to use the current category id rather than hard-coding it.
class MovieCategory(models.Model):
category = models.ForeignKey(Category)
movie = models.ForeignKey(Movie)
prefix = models.ForeignKey('Prefix', limit_choices_to={'category_id': '16'},
blank=True, null=True)
number = models.DecimalField(verbose_name='Movie Number', max_digits=2,
blank=True, null=True, decimal_places=0)
Is it possible to refer to the id of the category ForeignKey somehow?
After hours of reading semi related questions I finally figured this out.
You can't self reference a Model the way I was trying to do so there is no way to make Django act the way I wanted using limit_choices_to because it can't find the id of a different ForeignKey in the same model.
This can apparently be done if you change the way Django works, but a simpler way to solve this was to make changes to admin.py instead.
Here is what this looks like in my models.py now:
# models.py
class MovieCategory(models.Model):
category = models.ForeignKey(Category)
movie = models.ForeignKey(Movie)
prefix = models.ForeignKey('Prefix', blank=True, null=True)
number = models.DecimalField(verbose_name='Movie Number', max_digits=2,
blank=True, null=True, decimal_places=0)
I simply removed limit_choices_to entirely.
I found a similar problem here with the solution posted by Kyle Duncan. The difference though is that this uses ManyToMany and not ForeignKey. That means I had to remove filter_horizontal = ('prefix',) under my class MovieCategoryAdmin(admin.ModelAdmin): as that is only for ManyToMany fields.
In admin.py I had to add from django import forms at the top to create a form. This is how the form looks:
class MovieCategoryForm(forms.ModelForm):
class Meta:
model = MovieCategory
fields = ['prefix']
def __init__(self, *args, **kwargs):
super(MovieCategoryForm, self).__init__(*args, **kwargs)
self.fields['prefix'].queryset = Prefix.objects.filter(
category_id=self.instance.category.id)
And my AdminModel:
class MovieCategoryAdmin(admin.ModelAdmin):
"""
Admin Class for 'Movie Category'.
"""
fieldsets = [
('Category', {'fields': ['category']}),
('Movie', {'fields': ['movie']}),
('Prefix', {'fields': ['prefix']}),
('Number', {'fields': ['number']}),
]
list_display = ('category', 'movie', 'prefix', 'number')
search_fields = ['category__category_name', 'movie__title', 'prefix__prefix']
form = MovieCategoryForm
This is exactly how Kyle describes it in his answer, except I had to add fields = ['prefix'] to the Form or it wouldn't run. If you follow his steps and remember to remove filter_horizontal and add the fields you're using it should work.
Edit: This solution works fine when editing, but not when creating a new entry because it can't search for the category id when one doesn't exits. I am trying to figure out how to solve this.
Another approach, if you don't want to add a custom ModelForm, is to handle this in your ModelAdmin's get_form() method. This was preferable for me because I needed easy access to the request object for my queryset.
class StoryAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(StoryAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['local_categories'].queryset = LocalStoryCategory.\
objects.filter(office=request.user.profile.office)
return form
Keep in mind that limit_choices_to supports "Either a dictionary, a Q object, or a callable returning a dictionary or Q object" and should theoretically support any lookup that can be done using django's queryset filtering. A potential solution would then be filtering based on some property of the category that you control such as a slug field.
class MovieCategory(models.Model):
category = models.ForeignKey(Category)
movie = models.ForeignKey(Movie)
prefix = models.ForeignKey('Prefix', blank=True, null=True,
limit_choices_to=Q(category__slug__startswith='movie'))
number = models.DecimalField(verbose_name='Movie Number', max_digits=2,
blank=True, null=True, decimal_places=0)
I had the same question and your self-answer helped me get started. But I also found another post (question-12399803) that completed the answer, that is, how to filter when creating a new entry.
In views.py
form = CustomerForm(groupid=request.user.groups.first().id)
In forms.py
def __init__(self, *args, **kwargs):
if 'groupid' in kwargs:
groupid = kwargs.pop('groupid')
else:
groupid = None
super(CustomerForm, self).__init__(*args, **kwargs)
if not groupid:
groupid = self.instance.group.id
self.fields['address'].queryset = Address.objects.filter(group_id=groupid)
So, whether adding a new customer or updating an existing customer, I can click on a link to go add a new address that will be assigned to that customer.
This is my first answer on StackOverflow. I hope it helps.
I have an application that makes use of Django's UserProfile to extend the built-in Django User model. Looks a bit like:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
# Local Stuff
image_url_s = models.CharField(max_length=128, blank=True)
image_url_m = models.CharField(max_length=128, blank=True)
# Admin
class Admin: pass
I have added a new class to my model:
class Team(models.Model):
name = models.CharField(max_length=128)
manager = models.ForeignKey(User, related_name='manager')
members = models.ManyToManyField(User, blank=True)
And it is registered into the Admin:
class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'manager')
admin.site.register(Team, TeamAdmin)
Alas, in the admin inteface, when I go to select a manager from the drop-down box, or set team members via the multi-select field, they are ordered by the User numeric ID. For the life of me, I can not figure out how to get these sorted.
I have a similar class with:
class Meta:
ordering = ['name']
That works great! But I don't "own" the User class, and when I try this trick in UserAdmin:
class Meta:
ordering = ['username']
I get:
django.core.management.base.CommandError: One or more models did not validate:
events.userprofile: "ordering" refers to "username", a field that doesn't exist.
user.username doesn't work either. I could specify, like image_url_s if I wanted to . . . how can I tell the admin to sort my lists of users by username? Thanks!
This
class Meta:
ordering = ['username']
should be
ordering = ['user__username']
if it's in your UserProfile admin class. That'll stop the exception, but I don't think it helps you.
Ordering the User model as you describe is quite tricky, but see http://code.djangoproject.com/ticket/6089#comment:8 for a solution.
One way would be to define a custom form to use for your Team model in the admin, and override the manager field to use a queryset with the correct ordering:
from django import forms
class TeamForm(forms.ModelForm):
manager = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Team
class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'manager')
form = TeamForm
This might be dangerous for some reason, but this can be done in one line in your project's models.py file:
User._meta.ordering=["username"]
For me, the only working solution was to use Proxy Model. As stated in the documentation, you can create own proxy models for even built-in models and customize anything like in regular models:
class OrderedUser(User):
class Meta:
proxy = True
ordering = ["username"]
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
After that, in your model just change Foreign Key to:
user = models.OneToOneField(OrderedUser, unique=True)
or even more suitable
user = models.OneToOneField(OrderedUser, unique = True, parent_link = True)