Models.py:
class Comment(models.Model):
user = models.ForeignKey(User)
document = models.ForeignKey(Document)
section = models.ForeignKey(Section, null=True, blank=True)
description = models.TextField(null=True, blank=True)
Forms.py:
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ('section', 'description')
Each Comment belongs to a Section of a Document. Each Document hasmany Sections. However, the ModelChoiceField printed out by Django will contain Sections for ALL Documents.
How do I tell Django to only print the Sections that belong to a particular Document?
I looked at ModelFormSets - Changing the queryset but I don't think it's quite what I'm after.
If all you need to do is adjust the admin site you can override the formfield_for_foreignkey method on your django admin class.
From the docs:
ModelAdmin.formfield_for_foreignkey(self,db_field, request, **kwargs)
The formfield_for_foreignkey method on a ModelAdmin allows you to override the
default formfield for a foreign key field. For example, to return a subset
of objects for this foreign key field based on the user:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This uses the HttpRequest instance to filter the Car
foreign key field to only display the cars owned by the User instance.
I think you want to change the ModelChoiceField's queryset and not the queryset of the formset.
Related
SOLUTION AT THE BOTTOM
Problem: Django form populating with list of objects rather than values
Summary: I have 2 models Entities and Breaks. Breaks has a FK relationship to the entity_id (not the PK) on the Entities model.
I want to generate an empty form for all the fields of Breaks. Generating a basic form populates all the empty fields, but for the FK it generates a dropdown list of all objects of the Entities table. This is not helpful so I have excluded this in the ModelForm below and tried to replace with a list of all the entity_ids of the Entities table. This form renders as expected.
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
exclude = ('entity',)
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all().values_list('entity_id', flat=True))
The below FormView is the cbv called by the URL. As the below stands if I populate the form, and for the FK column entity_id choose one of the values, the form will not submit. By that field on the form template the following message appears Select a valid choice. That choice is not one of the available choices.
class ContactFormView(FormView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
My initial thoughts were either that the datatype of this field (string/integer) was wrong or that Django needed the PK of the row in the Entities table (for whatever reason).
So I added a post function to the FormView and could see that the request.body was populating correctly. However I can't work out how to populate this into the ModelForm and save to the database, or overcome the issue mentioned above.
Addendum:
Models added below:
class Entity(models.Model):
pk_securities = models.AutoField(primary_key=True)
entity_id = models.CharField(unique=True)
entity_description = models.CharField(blank=True, null=True)
class Meta:
managed = False
db_table = 'entities'
class Breaks(models.Model):
pk_break = models.AutoField(primary_key=True)
date = models.DateField(blank=True, null=True)
entity = models.ForeignKey(Entity, on_delete= models.CASCADE, to_field='entity_id')
commentary = models.CharField(blank=True, null=True)
active = models.BooleanField()
def get_absolute_url(self):
return reverse(
"item-update", args=[str(self.pk_break)]
)
def __str__(self):
return f"{self.pk_break}"
class Meta:
managed = False
db_table = 'breaks'
SOLUTION
Firstly I got this working by adding the following to the Entity Model class. However I didn't like this as it would have consequences elsewhere.
def __str__(self):
return f"{self.entity_id}"
I found this SO thread on the topic. The accepted answer is fantastic and the comments to it are helpful.
The solution is to subclass ModelChoiceField and override the label_from_instance
class EntityChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.entity_id
I think your problem is two fold, first is not rendering the dropdown correctly and second is form is not saving. For first problem, you do not need to do any changes in ModelChoiceField queryset, instead, add to_field_name:
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all(), to_field_name='entity_id')
Secondly, if you want to save the form, instead of FormView, use CreateView:
class ContactFormView(CreateView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
model = Breaks
In Django, the request object passed as parameter to your view has an attribute called "method" where the type of the request is set, and all data passed via POST can be accessed via the request. POST dictionary. The view will display the result of the login form posted through the loggedin. html.
I am trying to have a custom form on django admin for my ModelB, with fields taken from other ModelA.
models.py
class ModelA(models.Model):
source = models.CharField(max_length=80)
keys = ArrayField(
models.CharField(max_length=50)
)
class ModelB(models.Model):
characteristic_keys = JSONField()
forms.py
class ModelBForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
queryset = ModelA.objects.all()
dynamic_fields = [(x.source, x.keys) for x in queryset]
# New fields to be shown on admin =>
# Field name => "source" from modelA
# Field type => multiple choice with options => "keys" from modelA
for field in dynamic_fields:
self.fields[field[0]] = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,
choices=field[1])
def save(self, commit=True):
# ...do something with extra_field here...
return super().save(commit=commit)
class Meta:
model = Workflow
fields = "__all__"
admin.py
class ModelBAdmin(admin.ModelAdmin):
form = ModelBForm
admin.site.register(ModelB, ModelBAdmin)
I want a single form for ModelB on django admin, with dynamic "source" fields takes from ModelA, with multiple choice options from their corresponding "key" values in modelB.
I have tried to keep information clear and understandable, please let me know if I have missed any information that might be needed to understand the problem. Any ideas to deal this problem would be a great help!
I have a form that allows the user to pick several vans (many-to-many relationship). Each van has a boolean attribute named "available". I want to only show the vans whose "available" attribute is set to "True". How do I do this in the forms.py file?
I know that this could possibly be done in the template, but I did not want to create a new form-template with each individual field written out. I wanted to know if this functionality could be done in the forms.py file or in the class based view. I believe that doing it that way would be a bit cleaner. I've look into the validators but I don't think this is the way to go. Maybe I need to run a query set in the form file that checks the attribute before passing it to the form template?
views.py
def post(self, request):
"""Take in user data, clean it, and then post it to the database."""
form = self.form_class(request.POST) # pass in the user's data to that was submitted in form
if form.is_valid():
trip = form.save(commit=False) # create an object so we can clean the data before saving it
# now get the clean and normalize the data
first_name = form.cleaned_data['first_name']
last_name = form.cleaned_data['last_name']
trip_start = form.cleaned_data['trip_start']
trip_end = form.cleaned_data['trip_end']
van_used = form.cleaned_data['van_used']
trip.save()
forms.py
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
models.py
class trips(models.Model):
class Meta:
verbose_name_plural = "trips"
van_used = models.ManyToManyField(vans)
class vans(models.Model):
class Meta:
verbose_name_plural = "vans"
vanName = models.CharField(max_length=30, unique=True, blank=False)
available = models.BooleanField(default=True, blank=False)
# set up how the vans will be referenced in the admin page
def __str__(self):
return self.vanName
The final form that is rendered would only show the vans whose "available" attribute is set to True. Thanks in advance.
You have to override queryset for van_used field in form like this.
class TripForm(forms.ModelForm):
"""This class will be used to build trips."""
class Meta:
"""Specifying the database and fields to use."""
model = trips
fields = ['first_name', 'last_name', 'comments','trip_start', 'trip_end', 'van_used']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['van_used'].queryset = vans.objects.filter(available=True)
I use Django 1.7.11. I have models:
#I use django-categories app here
class Category(CategoryBase):
pass
class Advertisment(models.Model):
title = models.CharField(max_length=255, blank=True)
category = models.ForeignKey(Category, related_name='category')
all_categories = models.ManyToManyField(Category, blank=True, related_name='all_categories')
I need field "all_categories" contains "category" and all it's parent categories. I tried to use post_save, but it doesn't change any value. It even doesn't change title field. It doesn't work when I create model throught admin interface and works with custom form.
#receiver(post_save, sender=Advertisment, dispatch_uid="update_stock_count")
def update_stock(sender, instance, **kwargs):
categ = instance.category
instance.all_categories.add(categ)
for parent in categ.get_ancestors():
if parent not in instance.all_categories.all():
instance.all_categories.add(parent)
m2m_changed doesn't help too because ManyToManyField is empty and has no changes. How can I add a value from ForeignKey to ManyToMany field? What should I do in order to it works in admin interface.
I've found the solution. In admin class need to add a function save_model like this:
class AdvertismentAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if obj.category:
category_list=[]
category = obj.category
category_list.append(category)
for parent in category.get_ancestors():
if parent not in category_list:
category_list.append(parent)
form.cleaned_data['all_categories'] = category_list
super(AdvertismentAdmin, self).save_model(request, obj, form, change)
Is there a way to dynamically change the values displayed in SelectFields for ForeignKeys in Django Admin? Without having to write some custom JS that does it.
Say, I have two models: Topic and AreaOfDiscussion.
Topic has an area of discussion, as in
class Topic(models.Model):
area = models.ForeignKey(AreaOfDiscussion, null=False)
Now I'm adding a Lecture model, which must have both a topic and an area.
class Lecture(models.Model):
area = models.ForeignKey(AreaOfDiscussion, null=False)
topic = models.ForeignKey(Topic, null=False)
I want the user (of the admin panel) to be able to choose an area first, then based on the choice, give him/her a list of available Topics for the chosen Area. Is this achievable?
You can use django-smart-selects package:
https://github.com/digi604/django-smart-selects
Firstly set topic = models.Fk(..., null=True)
class AdminLecture(admin.ModelAdmin):
object = None
def get_object(self, request, object_id):
self.object = super(AdminLecture, self).get_object(request, object_id)
return self.object
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if self.object and db_field.name == 'topic':
kwargs['queryset'] = Topic.objects.filter(area=self.object.area)
return super(AdminLecture, self).formfield_for_foreignkey(db_field, request, **kwargs)
then push save and cotinue edit