Render dropdown in django view using values from postgres arrayfield - python

Here is an example from the model:
class Shipment(models.Model):
shipment_id = models.BigAutoField(null=False, primary_key=True)
potential_shipping_dates = ArrayField(models.DateField(), verbose_name='Ship Dates', null=True)
Here is what I'm sort of attempting in my form:
class ShippingForm(forms.Form):
potential_shipping_dates = forms.ModelChoiceField(queryset=Shipment.objects.all())
def __init__(self, *args, **kwargs):
super(ShippingForm, self).__init__(*args, **kwargs)
And here is where my form is added to context:
context['shippingForm'] = ShippingForm(initial=??what_goes_here_maybe??)
My form renders fine but I want to show a dropdown with a date for each option.

Okay this is a bit complex, but I think I understand what you're trying to do, and where you're going wrong.
So you have a Shipment model, and each Shipment instance has a field with a few different potential_shipping_dates.
Say you have 2 shipments:
IN : ship1 = Shipment.objects.first()
OUT:
IN : ship1.potential_shipping_dates
OUT: ['01/01/2021', '02/02/2021']
IN : ship2 = Shipment.objects.last()
OUT:
IN : ship2.potential_shipping_dates
OUT: ['03/03/2021', '04/04/2021']
Now, do you want the dropdown to have all 4 dates as possibilities, and that will select the Shipment?
Or do you want to select a date after selecting the shipment in the form?
^^ Answered in comments
Okay so you will need to pass the instance through to the form:
views.py
# Inherit from Django's UpdateView to have `instance` passed through to the form
class ShippingFormView(UpdateView):
model = Shipment
form_class = ShippingForm
# Or if you don't want to inherit from inherit from UpdateView
class ShippingFormView(Blah):
model = Shipment
form_class = ShippingForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['instance'] = self.get_object()
return kwargs
# Or if you're using function based views
def shipping_form_view(request, pk):
shipment = get_object_or_404(Shipment, pk=pk)
form = ShippingForm(request, instance=shipment)
...
forms.py
class ShippingForm(forms.Form):
potential_shipping_dates = forms.ChoiceField(choices=[])
def __init__(self, *args, instance, **kwargs):
super(ShippingForm, self).__init__(*args, **kwargs)
self.fields['potential_shipping_dates'].choices = ((dt, dt) for dt in instance.potential_shipping_dates)
ModelChoiceFields are used when selecting an object, not an attribute on one.

Related

Error while saving foreign key data in database using Django Model Form

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)

Form Problems - Setting Initial Value

I am trying to set the initial value of a field on a form. The field is not part of the model, but when I try and set it to a value the field is blank. From my research it could be because the form is "bound" which makes some sense to me, but in this case the field is not part of the model.
My form:
#Form for editing profile
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid, required=False)
The model:
class Cat(models.Model):
name = models.CharField(max_length=34,null=False)
From the view it is called like this:
catform = CatForm(request.POST, pictureid=instance.id)
I was expecting it to set the field to the value of the initial attribute, but it doesn't. I have tried testing it by directly adding a string, but doesn't set.
This is what seems to be working for me:
class CatForm(forms.ModelForm):
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid)
I also needed to drop the "request.POST" from the call to this when initiating the form.
If you want to render the pictureid in GET request, then you can try like this:
catform = CatForm(initial={'pictureid': instance.id})
For GET request, you don't need to override the __init__ method.
But, if you want to use the Catform in POST request, to use the value of pictureid somewhere else(lets say in save method), then you will need to override __init__ method here.
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.pictureid = picid
def save(self, *args, **kwargs):
print(self.pictureid) # if you want to use it in save method
return super().save(*args, **kwargs)

Provide different kwargs for each form in formset

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

Validation error for one to one relationship in django modelfrom

I have 2 models which are related to each other using One to One Relation. How can i display the validation error if object of one model is already related to another.
Suppose i have 2 models
Class Place(models.Model):
field1 = CharField()
field2 = CharField()
Class Restaurant(models.Model):
related_field = OneToOneField(Place)
field3 = CharField()
In my case For one place one Restaurant. If user submits new Restaurant for the same place, then a validation should raise
If Restaurant object already attached to Place object, so for second Restaurant for same Place object i want to display validation
In admin we usually see this message "Restaurent with this Place already exists." But how can i achieve the same this using modelform with generic views
class RestaurantCreateView(CreateView):
form_class = RestaurantForm
template_name = 'restaurant_create.html'
def get_success_url(self):
return reverse_lazy('restuarants', args=[self.object.id])
def get_place(self, **kwargs):
id = self.kwargs['pk']
place = Place.objects.get(id=id)
return place
def get_context_data(self, **kwargs):
context = super(RestaurantCreateView, self).get_context_data(**kwargs)
place = self.get_place()
context['place'] = place
return context
def form_valid(self, form):
self.object = form.save(commit=False)
place = self.get_place()
self.object.place = place
self.object.save()
return HttpResponseRedirect(self.get_success_url())
How can perform One To One relation validation in class based generic views?
Then you need to pass the Place object into your Restaurant form. The form validation has no access to these things defined in the view unless you explicitly pass it that data.
class MyForm(forms.ModelForm):
class Meta:
model = Restaurant
def __init__(self, *args, **kwargs):
self.place = kwargs.pop('place', None)
super(MyForm, self).__init__(*args, **kwargs
def clean_related_field(self):
place = self.place
try:
place.restaurant
except Restaurant.DoesNotExist:
return place
raise forms.ValidationError("Restaurant already exists for that book")
class MyView(...):
form_class = MyForm
def get_form_kwargs(self):
kwargs = super(MyView, self).get_form_kwargs()
kwargs['place'] = Place.objects.get(id=self.kwargs['pk'])
return kwargs
{% if form.related_field.errors %}.........{% endif %}
Another approach is to fake validation in form_valid() and set the errors dict, and return the response. This method may be preferable since it's much more contained. Also especially if you are not using the form field anyways, you might as well add an error flag to the context.
class MyView():
def form_valid(self, form):
if Restaurant.objects.filter(related_field=self.get_place()):
ctx = self.get_context_data(form=form)
ctx['massive_error'] = True
return self.render_to_response(ctx)

filtering dropdown values in django admin

class Foo(models.Model):
title = models.TextField()
userid = models.IntegerField()
image = models.CharField(max_length=100)
def __unicode__(self):
return self.title
class Bar(models.Model):
foo = models.ForeignKey(Foo, related_name='Foo_picks', unique=True)
added_on = models.DateTimeField(auto_now_add=True)
In Django admin add_view:
def add_view(self, *args, **kwargs):
self.exclude = ("added_on",)
self.readonly_fields = ()
return super(Bar, self).add_view(*args, **kwargs)
So, Field shows in the admin add view is foo Which is a drop down list and shows all the titles. Some title of Foo remains empty or ''. So, drop down list have lots of empty value because it title is empty. I want to filter out those empty values.
You can provide your own form for ModelAdmin, with custom queryset for foo field.
from django import forms
from django.contrib import admin
#Create custom form with specific queryset:
class CustomBarModelForm(forms.ModelForm):
class Meta:
model = Bar
fields = '__all__'
def __init__(self, *args, **kwargs):
super(CustomBarModelForm, self).__init__(*args, **kwargs)
self.fields['foo'].queryset = Foo.objects.filter(title__isnull=False)# or something else
# Use it in your modelAdmin
class BarAdmin(admin.ModelAdmin):
form = CustomBarModelForm
Something like this...
docs
for django 1.6:
For foreign key:
https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#ModelAdmin.formfield_for_foreignkey
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "title":
kwargs["queryset"] = Foo.objects.filter(title__isnull=False)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
I stumbled across this question when looking for a solution to filter dropdown options on the fly in the admin interface based on the selection in another field -- not based on a pre-filtered list at page load. The solution I found was this library: https://github.com/digi604/django-smart-selects which is an app that uses ajax calls and allows chain filtering to multiple levels. Works like a charm for me. -HTH
You could subclass your own model.ModelAdmin and create a custom field for your ChoiceField...
class CustomForm(model.ModelForm):
class Meta:
model = Foo
foo = forms.ChoiceField(widget=forms.Select, initial=self.foo_queryset)
def foo_queryset(self):
return Foo.objects.filter(xy)...
class FooAdmin(model.ModelAdmin):
form = CustomForm

Categories