I have a model for adding entries of Mobile apps:
class MobileApp(models.Model):
name = models.CharField(max_length=255)
description = models.TextField()
images = models.ManyToManyField(Image, blank=True)
In Django Admin, what i am trying to do is filter the images that are listed in the list to prevent django from loading all images in that table which are quite alot.
So what i currently do is the following:
class MobileAppAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MobileAppAdmin, self).get_form(request, obj, **kwargs)
if obj:
form.base_fields['images'].queryset = Image.objects.filter(pk__in=obj.images.all())
else:
form.base_fields['images'].queryset = Image.objects.filter(pk=0)
return form
But when submitting the form, adding a new image, what happens is the following:
Select a valid choice. XYZ is not one of the available choices.
On the images field.
How can i make this work? i have lots of fields that need the same move as django keeps loading all the records to populate the lists for relations.
Thanks
Don't limit options when form being submitted.
class MobileAppAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MobileAppAdmin, self).get_form(request, obj, **kwargs)
if request.method == 'GET':
if obj:
form.base_fields['images'].queryset = Image.objects.filter(pk__in=obj.images.all())
else:
form.base_fields['images'].queryset = Image.objects.filter(pk=0)
return form
Related
I have two Models for my Project, 1. Category Model and 2. Course Model
Course Model has a Foreign Key reference with my Category Model as shown below.
class Category(models.Model):
categoryname = models.CharField(max_length=200,null=True,blank=True, default="")
class Courses(models.Model):
coursename = models.CharField(max_length=200,null=True,blank=True, default="")
course_category = models.ForeignKey(Category, related_name="courses", blank=True,null=True,on_delete=models.CASCADE)
logo = models.ImageField(upload_to='courselogos', null=True, blank=True)
Initially I was using HTML form and will be able to save the Course data under a Particular Category to the database as:
def add_course(request):
if request.method == 'POST':
course_name = request.POST.get('coursname')
categoryid = request.POST.get('category_id')
category = Category.object.get(id=category_id)
course_logo = request.FILES.get('logo')
course = Courses(coursename=course_name, course_category=category, logo= course_logo)
course.save()
return redirect('/all_category')
Later I decided to move on using Django Model forms and I tried to implement the code as follows
class AddCourseForm(forms.ModelForm):
class Meta:
model = Courses
fields = ('coursename', 'course_category', 'logo')
widgets = {
'coursename' : forms.TextInput(attrs={'class':'form-control'}),
}
def __init__(self, *args, **kwargs):
category_id = kwargs.pop('category_id',1)
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category']=forms.ModelChoiceField(widget=forms.TextInput(), queryset=Category.objects.filter(id=category_id))
Later in the view I have saved the data as
def add_course(request):
if request.method == 'POST':
addcourse = AddCourseForm(request.POST, request.FILES)
if addcourse.is_valid():
addcourse.save()
return redirect('/all_category')
On my HTML page I am passing the input to the 'course_category' inputfield as 1,2,3....etc as the category_id value
I have rendered the field in the form as
{{form.course_category}}
On Submitting the form when my 'course_category' inputfield has value as 1, it saves the data to the database but when the inputfield value is 2 then it is not even entering to the if condition of addcourse.is_valid() in the view function.
As I'm new the Django I'm not able to find the right way to get the ForeignKey value dynamically save the data in reference to that Category. Also I want to populate the same data back to the form in case of edit.
Please guide, thanks in advance.
After debugging the Code a little bit, I modified the init function in the AddCourseForm class as mentioned below that solved my issue but I am not it is the right way to do this or not
def __init__(self, *args, **kwargs):
category_id = None
for key in args[0:1]:
category_id = args[0].get('course_category')
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category']=forms.ModelChoiceField(widget=forms.TextInput(), queryset=Category.objects.filter(id=category_id))
I don't think doing this should be that difficult, here is how you would set the course_category options in the form normally:
# forms.py
class AddCourseForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
self.course_categories = Category.objects.all()
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category'].queryset = self.course_categories
If you want to set a particular category in the form the you can pass an initial value in your view:
# views.py
def add_course(request, pk):
# note: you can pass the category primary key to your view, you need to
# specify this in your URLs and then your template
course_category = Category.objects.get(pk=pk)
form = AddCourseForm(initial={'course_category': course_category})
If you then want to kill all other options entirely, you can use the initial value to set your filter:
# forms.py
class AddCourseForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category'].queryset = Category.objects.filter(
pk=self.fields['course_category'].initial)
How to hide field for specific record in djano-admin?
For example if I have a model
class Book(models.Model):
title = models.CharField(..., null=True)
author = models.CharField(...)
I want to hide an author in admin panel for record with pk = 1. I found the solution as
class BookAdmin(admin.ModelAdmin):
list_display = ("pk", "get_title_or_nothing")
def get_form(self, request, obj=None, **kwargs):
if obj.pk == "1":
self.exclude = ("author", )
form = super(BookAdmin, self).get_form(request, obj, **kwargs)
return form
It works well untill I am coming back from record with pk == 1 to other records, in this case all records in table have hided author field.
The reason this is happening is because once the "author" field gets appended to self.exclude, it just stays there.
For Django >= 1.11:
A better place to exclude fields dynamically is in ModelAdmin.get_exclude. It was introduced in Django 1.11.
def get_exclude(self, request, obj=None, **kwargs):
if obj and obj.pk == 1:
# don't modify self.exclude
# instead return it with the extra added field
return self.exclude + ('author',)
return self.exclude
For Django < 1.11:
For older versions, you can do something like this:
class BookAdmin(...):
exclude = (# common excluded fields)
original_exclude = exclude
def get_form(self, request, obj=None, **kwargs):
if obj and obj.pk == 1:
self.exclude += ('author',)
else:
# change it back to the original_exclude
# for other objects
self.exclude = self.original_exclude
form = super(BookAdmin, self).get_form(request, obj, **kwargs)
return form
I am trying to create a dynamic choice field. I have a view that creates a list of tuples. The first value of the tuple is the primary key of the object ServiceWriter while the second value is the name of the ServiceWriter. The list then gets passed into the form class. When I make the selection and submit the page the form is decided to be not valid and the following form error is printed in the shell: "Select a valid choice. (First value of tuple. ie 1,2,3..) is not one of the available choices."
forms.py
class CreateAdvancedRO(forms.Form):
service_writer = forms.ChoiceField()
def __init__(self, writer_choices, *args, **kwargs):
super(CreateAdvancedRO, self).__init__(*args, **kwargs)
self.fields['service_writer'].choices = writer_choices
self.helper = FormHelper()
self.helper.form_id = 'id-create-advanced-ro'
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Open Repair Order'))
Note: I am not using a ModelForm.
views.py
class CreateAdvancedRO(View):
form_class = CreateAdvancedRO
writer_form = CreateServiceWriter
add_line_form = AddJobLine
def post(self, request):
writer_choices = []
form = self.form_class(writer_choices, request.POST)
print(form.errors)
if form.is_valid():
'''Do something'''
else:
writer_choices = []
try:
writers = ServiceWriter.objects.filter(user=request.user)
for writer in writers:
writer_choices.append((str(writer.id), writer.name))
except ObjectDoesNotExist:
pass
form = self.form_class(writer_choices, request.POST)
writer_form = self.writer_form()
add_line_form = self.add_line_form()
return render(request, 'free/advanced_create.html', {'form': form, 'writer_form': wri
'add_line_form': add_line_form})
I have tried both of the following in the view:
writer_choices.append((str(writer.id), writer.name)) and
writer_choices.append((writer.id, writer.name))
Here is the ServiceWriter model, just in case.
class ServiceWriter(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20, blank=False)
def __str__(self):
return str(self.name)
Any thoughts?
Thanks for the help.
It looks like you're trying to validate the form against an empty list of choices. Have you tried populating writer_choices before instantiating or attempting to validate the form?
I am trying to use a formset to create forms for a set of timeframes related to dates:
class Event(models.Model):
date = models.DateField()
class TimeFrame(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
event = models.ForeignKey('Event')
I have code that gets me a queryset of timeframes for each event and added a kwarg to pass this into my form:
class SelectDatesForm(forms.Form):
timeframes = forms.ModelChoiceField(queryset=HostTimeFrame.objects.none())
def __init__(self, *args, **kwargs):
qs = kwargs.pop('timeframes')
super(SelectDatesForm, self).__init__(*args, **kwargs)
self.fields['timeframes'].queryset = qs
Now I'm trying to construct a formset that lets me show timeframes for multiple events on one page. I already found this question, explaining how to pass initial data, for serveral forms, but its not the same as passing it to a queryset.
Also there is this new function from django 1.9 but it doesnt allow me to get different querysets for each form.
UPDATE:
I got the solution from the answer working, however, whenever im running formset.is_valid() i get the error:
Select a valid choice. That choice is not one of the available
choices.
Here is what I do in my view:
timeframes = [HostTimeFrame.objects.all()]
SelectDatesFormset = formset_factory(form=SelectDatesForm, extra=len(timeframes), formset=BaseSelectDatesFormSet)
if request.method == 'POST':
formset = SelectDatesFormset(request.POST, form_kwargs={'timeframes_list': timeframes})
if formset.is_valid():
# do something with the formset.cleaned_data
print(formset)
pass
else:
formset = SelectDatesFormset(form_kwargs={'timeframes_list': timeframes})
Ive been trying for hours to find where this actual validation is done, but i cant find it for the live of me.
Edit: I tested this with the singular form, and i have the same issue, I created a new question for this here.
UPDATE:
Only partial solution, see question.
Solved it myself:
First I created a BaseFormSet:
class BaseSelectDatesFormSet(BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super(BaseSelectDatesFormSet, self).get_form_kwargs(index)
kwargs['timeframes'] = kwargs['timeframes_list'][index]
return kwargs
Then I could pass the list of timeframes in the view:
SelectDatesFormset = formset_factory(form=SelectDatesForm, extra=4, formset=BaseSelectDatesFormSet)
formset = SelectDatesFormset(form_kwargs={'timeframes_list': timeframes})
Finally I had to update my form init to pop the list as well so the super constructor doesnt complain about unwanted kwargs:
def __init__(self, *args, **kwargs):
qs = kwargs.pop('timeframes')
qs_list = kwargs.pop('timeframes_list')
super(SelectDatesForm, self).__init__(*args, **kwargs)
self.fields['timeframes'].queryset = qs.order_by('start')
For peeps using Class Based View FormView along with form_class as formset or formset_factory, they can add an extra attribute as follows:
Pass form_kwargs in the get_form method by overriding it.
timeframes = [HostTimeFrame.objects.all()]
class SelectDatesView(FormView):
form_class = formset_factory(form=SelectDatesForm, extra=len(timeframes)
def get_form(self, form_class=None):
"""Override the method to add form kwargs. Returns an instance of the form to be used in this view."""
if form_class is None:
form_class = self.get_form_class()
return form_class(**self.get_form_kwargs(), form_kwargs={"timeframes": timeframes})
One can access it directly in the __init__ method's kwargs.
def __init__(self, *args, **kwargs):
super(SelectDatesForm, self).__init__(*args, **kwargs)
qs = kwargs.get('timeframes')
self.fields['timeframes'].queryset = qs.order_by('start')
I have a django app that into the model has a json field looks like this
from json_field import JSONField
from django.db import models
class C(models.Model):
name = models.CharField(max_length=255)
jf = JSONField(null=False)
There is a form that display this as follow
class Edit(forms.Form):
name = forms.CharField()
def __init__(self, *args, **kwargs):
if 'extra' in kwargs:
extra = kwargs.pop('extra')
super(Edit, self).__init__(*args, **kwargs)
for k, v in extra.iteritems():
self.fields['%s' % k] = v
else:
super(Edit, self).__init__(*args, **kwargs)
The view will load the json field jf and send it to the Form as initial data, as well
will send all the necessary fields as extra.
def edit_model(request, pk):
obj = get_object_or_404(models.C, pk=pk)
initial = model_to_dict(obj)
form = Edit(request.POST or None, initial=initial, extra=initial['jf'])
if request.method == 'POST':
if form.is_valid():
.....
# what is the best practice here ?
# intersect cleaned data with the jf fields ?!
# have an external entity that does this ?!
# is anything built into django that can help
thanks!
If I understood you can try this custom widget's form of django It does JSON to formfields and formfields -> JSON after save