Help with django bugtrack comments system using ModelForm - python

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

Django Form: Only show manytomany objects from logged in user

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.

Specific Queryset for Input on Django ModelAdmin Change Form

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

How to order readonly M2M fields in django admin

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']

Django ForeignKey limit_choices_to a different ForeignKey id

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.

Django Admin: Ordering of ForeignKey and ManyToManyField relations referencing User

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)

Categories