Django forms class that does not refresh - django

I have noticed a weird behavior in one of my Django applications, running with apache/mod_wsgi. There is a screen that displays a form, basically a dropown list with a list of availability to schedule a given site, computed from the difference between a given weekly capacity (3 sites/wk) and the total number of sites already scheduled at a given week.
This form (ScheduleForm) is rendered from following view (followup/views.py):
def schedule(request, site_id):
site = Site.objects.get(pk=site_id)
if request.method == 'POST':
form = ScheduleForm(request.POST)
if form.is_valid():
(year, week) = request.POST['available_slots'].split('/')
site.scheduled = week2date(int(year), int(week[1:]))
site.save()
return HttpResponseRedirect('/stats/')
else:
form = ScheduleForm()
return render_to_response('followup/schedule_form.html',{
'form': form,
'site': site
}, context_instance=RequestContext(request))
Here is the form class (followup/forms.py):
class ScheduleForm(forms.Form):
"""
Temporary lists
"""
schedules = Site.objects.filter(
scheduled__isnull=False
).values('scheduled').annotate(Count('id')).order_by('scheduled')
integration = {}
available_integration = []
# This aggregates all schedules by distinct weeks
for schedule in schedules:
if schedule['scheduled'].strftime('%Y/W%W') in integration.keys():
integration[schedule['scheduled'].strftime('%Y/W%W')] += schedule['id__count']
else:
integration[schedule['scheduled'].strftime('%Y/W%W')] = schedule['id__count']
for w in range(12): # Calculates availability for the next 3 months (3months*4 weeks)
dt = (date.today() + timedelta(weeks=w)).strftime('%Y/W%W')
if dt in integration.keys():
capacity = 3-integration[dt]
else:
capacity = 3
if capacity>0:
available_integration.append([dt, capacity])
"""
Form
"""
available_slots = forms.ChoiceField(
[[slot[0], '%s (%s slots available)' % (slot[0], slot[1])] for slot in available_integration]
)
class IntegrateForm(forms.Form):
integrated_on = forms.DateField(widget=AdminDateWidget())
This actually works fine but the only problem is that the list of availability is not refreshed when a site is scheduled, unless I restart the apache process each time I schedule a site.
It's like if the availability list would be cached by the form class...
Any idea would be warmly welcomed. Thank you in advance for any kind of help.

This has to do with how python works and not django.
This code below
class ScheduleForm(forms.Form):
"""
Temporary lists
"""
schedules = Site.objects.filter(
scheduled__isnull=False
).values('scheduled').annotate(Count('id')).order_by('scheduled')
integration = {}
available_integration = []
will be evalutated only once - when the server starts.
You shouldn't do these things on the class level, but on the instance level.
Most likely inside the __init__ method of your form.
See the examples below:
Django - change min_length in form __init__?
django model form, restrict choices based on value in ForeignKey model
Django "dynamic" verification form (updated)

I have modified my code as follows and it's now working like a charm :) I provide it here in case it helps anyone with a similar problem.
class ScheduleForm(forms.Form):
available_slots = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(ScheduleForm, self).__init__(*args, **kwargs)
schedules = Site.objects.filter(
scheduled__isnull=False
).values('scheduled').annotate(Count('id')).order_by('scheduled')
integration = {}
available_integration = []
# This aggregates all schedules by distinct weeks
for schedule in schedules:
if schedule['scheduled'].strftime('%Y/W%W') in integration.keys():
integration[schedule['scheduled'].strftime('%Y/W%W')] += schedule['id__count']
else:
integration[schedule['scheduled'].strftime('%Y/W%W')] = schedule['id__count']
for w in range(12): # Calculates availability for the next 3 months (3months*4 weeks)
dt = (date.today() + timedelta(weeks=w)).strftime('%Y/W%W')
if dt in integration.keys():
capacity = 3-integration[dt]
else:
capacity = 3
if capacity>0:
available_integration.append([dt, capacity])
self.fields['available_slots'].choices = [[slot[0], '%s (%s slots available)' % (slot[0], slot[1])] for slot in available_integration]

Related

Use data from GET request in get_intial() and get_form_kwargs() of FormVIew

I am trying to refactor my code to inherit FormView instead of View. The view I'm working with receives values in the GET request. I retrieve the values in the get_context_data method and pass them through different functions to end up with a set of variables that I can pass in the context.
In short:
For the sake of example, the set of variables includes variables FOO and BAR. I need to initialise my form by passing variable FOO in the kwargs and additionally set my form field's initial value to BAR. I understand I should use the get_initial() and get_form_kwargs() methods to do this. I am just struggling with how to get FOO and BAR from the get_context_data method.
I tried adding FOO and BAR to the context dictionary:
context = super().get_context_data(**kwargs)
context["FOO"] = foo
context["BAR"] = bar
return context
And then calling it from the other methods:
def get_initial(self):
""" Get initial value for the form field """
initial = super(NameOfView, self).get_initial()
context = self.get_context_data()
initial_value = context["BAR"]
initial.update({'name': inital_value})
return initial
and the same for get_form_kwargs. But I get a RecursionError:
maximum recursion depth exceeded while calling a Python object
Any help understanding how I can acheive this will be appreciated
UPDATE: My Actual code is a bit more like this:*
class ConfirmTripView(FormView):
"""
Provides the user a set of choice options based on their search input in
the products.TripsView
"""
model = Booking
template_name = "bookings/trips_available.html"
form_class = DateChoiceForm
def __init__(self):
self.searched_date = None
self.passengers = None
self.destination_id = None
self.gte_dates = None
self.lt_dates = None
def convert_to_int(self, type_tuple):
""" Converts tuple value to integer """
type_int = int(''.join(type_tuple))
return type_int
def get_available_trips(self, destination, passengers):
""" Find trips with enough seats for searched no. of passengers """
available_trips = Trip.objects.filter(
destination=destination
).filter(seats_available__gte=passengers)
return available_trips
def get_trips_matched_or_post_date(self, date, destination, passengers):
"""
Returns trips that either match or are post- searched_date
Refine to trips with dates closest to searched_date
limit to 3 results
"""
available_trips = self.get_available_trips(destination, passengers)
gte_dates = available_trips.filter(date__gte=date)[:3]
return gte_dates
def get_trips_preceding_date(self, date, destination, passengers):
"""
Returns trips that are pre- searched_date
Refines to trips with dates closest to searched_date
limits to 3 results
"""
available_trips = self.get_available_trips(destination, passengers)
lt_dates = available_trips.filter(date__lt=date).order_by("-date")[:3]
return lt_dates
def make_timezone_naive(self, obj):
""" Turns date attribute to a time-zone naive date object """
date_attr = obj.date
date_string = date_attr.strftime("%Y-%m-%d")
datetime_naive = datetime.strptime(date_string, "%Y-%m-%d")
return datetime_naive
def get_trips_queryset(self, gte_dates, lt_dates):
""" Creates the queryset that will be used by the ModelChoiceField
in the DateChoiceForm """
# Merge both queries
trips = lt_dates | gte_dates
trips = trips.order_by('date')
return trips
def get_initial(self, **kwargs):
""" Takes values from get request and formulates variables
to be used in the form """
# Values from GET request
self.searched_date = self.request.GET.get('request_date')
self.passengers = self.request.GET.get('passengers')
self.destination_id = self.convert_to_int(
self.request.GET.get("destination")
)
# Return querysets for dates before/beyond searched_date respectively:
self.gte_dates = self.get_trips_matched_or_post_date(
self.searched_date,
self.destination_id,
self.passengers)
self.lt_dates = self.get_trips_preceding_date(
self.searched_date,
self.destination_id,
self.passengers)
naive_searched_date = datetime.strptime(self.searched_date, "%Y-%m-%d")
# Find the trip closest to the searched_date (for form initial value)
if self.gte_dates:
gte_date = self.gte_dates[0]
naive_gte_date = self.make_timezone_naive(gte_date)
if self.lt_dates:
lt_date = self.lt_dates[0]
naive_lt_date = self.make_timezone_naive(lt_date)
if (
naive_gte_date - naive_searched_date
> naive_searched_date - naive_lt_date
):
default_selected = lt_date
else:
default_selected = gte_date
else:
default_selected = gte_date
elif self.lt_dates:
lt_date = self.lt_dates[0]
default_selected = lt_date
else:
messages.error(
self.request,
"Sorry, there are no dates currently available for the"
"selected destination.",
)
# Get initial valuees for the form
initial = super(ConfirmTripView, self).get_initial()
initial.update({'trip': default_selected})
return initial
def get_form_kwargs(self, **kwargs):
""" Provides keyword arguemnt """
kwargs = super(ConfirmTripView, self).get_form_kwargs()
trips = self.get_trips_queryset(self.gte_dates, self.lt_dates)
kwargs.update({'trips': trips})
return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
destination = Product.objects.filter(id=self.destination_id)
context["passengers"] = self.passengers
context["destination_obj"] = destination
return context
def form_valid(self, form):
"""
Takes the POST data from the DateChoiceForm and creates an
Intitial Booking in the database
"""
booking = form.save(commit=False)
booking.status = "RESERVED"
booking.save()
trip = form.cleaned_data['trip']
destination = trip.destination
booking_line_item = BookingLineItem(
booking=booking,
product=destination,
quantity=self.request.GET.get("passengers")
)
booking_line_item.save()
return redirect('create_passengers', booking.pk)
First of all, bookmark this.
Second, get_initial() and get_context_data() solve 2 different problems:
get_initial is to pass initial values to a form.
get_context_data is to pass variables to a template
As you can see in above site, the form is injected into the template variables through get_context_data() and that's where your recursion problem comes from:
- get()
|- get_context_data() <----------------------------------\
|- get_form() |
|- get_form_kwargs() |
|- get_initial() --> you call get_context_data here ---/
Now, how your GET parameters and form should be working together is unclear from your question, but if you need some values from GET for initial form values, then get them inside get_initial().
UPDATE:
I'd not have a method get_queryset() with a signature like this, reason is that several views dealing with models also have a get_queryset() method with a different signature. get_trips() in this context makes a lot of sense
You've already extracted a few functionalities, which is good, but the "finding closest to searched date" can be extracted as well and it's result stored on self.
It's possible to use __range lookups which probably makes your logic easier. Change the semantics to "find trips between 30 days before and 30 days after search date". This is not the same but a good enough approach in practical terms.
If you're still stuck, let us know on what specifically.

MultipleObjectsReturned: get() returned more than one Driver -- it returned 3

I have a DRF API that is supposed to feed a frontend Android and IOS app. The API has three types of users namely: Driver, Client and Admin.
The client is supposed to create a booking record and get assigned a driver automatically by the API. Based on availability days and times. At the moment every day (Sun, Mon, Tue, Wed, Thu, Fri) is a working day and working hours range from 7:00 am to 9:00 pm with working hours inclusive of transition being an hour. For example, booking of 9:00am will take an hour, hence finishing time will be 10:00am, all factors inclusive. Many drivers can have work at the same time. The app should give feedback to the user if they try booking already picked slots.
My problem at this time is to loop through already existing drivers from drivers table, left join them to the booking table and and assign them one by one. I was able to assign one driver a job. But when I added drivers, it became difficult. There is something I am not getting well and I do not know what it is.
Here are my models.
""" models.py """
""" Helper function to check overlapping times """
def check_time_overlap(self, fixed_start, fixed_end, new_start, new_end):
time_overlap = False
if new_start == fixed_end or new_end == fixed_start: # edge case
time_overlap = False
elif (new_start >= fixed_start and new_start <= fixed_end) or \
(new_end >= fixed_start and new_end <= fixed_end): \
# innner limits
time_overlap = True
elif new_start <= fixed_start and new_end >= fixed_end: \
# outter limits
time_overlap = True
return time_overlap
""" Function to check overlapping bookings """
def overlapping_bookings(self):
if self.finishing_at <= self.booking_time:
raise ValidationError(
'Finishing times must be after booking times'
)
bookings = Booking.objects.filter(
booking_date=self.booking_date, driver_id=self.driver
)
if bookings.exists():
for booking in bookings:
""" Check whether date and time overlaps """
if self.check_time_overlap(
booking.booking_time, booking.finishing_at,
self.booking_time, self.finishing_at
):
""" If all drivers are allocated, raise an error \
message. """
raise ValidationError(
'All our drivers are booked at: ' +
str(booking.booking_date) + ', ' + str(
booking.booking_time) + '-' +
str(booking.finishing_at))
def save(self, *args, **kwargs):
self.calc_booking_total()
self.calc_finishing_at()
self.overlapping_bookings()
super(Booking, self).save(*args, **kwargs)
From the above models, I have written a function to check overlapping times for the same booking_date, booking_time and finishing_at times. This is inspired by ALEXANDRE PINTO on >https://alexpnt.github.io/2017/07/15/django-calendar/
Below are the serializers
# BOOKING SERIALIZER
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––#
class BookingSerializer(serializers.ModelSerializer):
package = serializers.SerializerMethodField()
vehicle_type = serializers.SerializerMethodField()
client_name = serializers.SerializerMethodField()
client_phone = serializers.SerializerMethodField()
class Meta:
model = Booking
# fields = '__all__'
exclude = ('driver',)
read_only_fields = (
'booking_total', 'booking_status',
'booked_at', 'finishing_at', 'client'
)
def get_package(self, obj):
return obj.service.package_name
def get_vehicle_type(self, obj):
return obj.service.vehicle_category.name
def get_client_name(self, obj):
return obj.client.name
def get_client_phone(self, obj):
return str(obj.client.phone)
""" Function to create booking based on authenticated client and available drivers """
def create(self, validated_data):
validated_data['client'] = self.context['request'].user
validated_data['driver'] = Driver.objects.get(active=True)
bookings = Booking.objects.create(**validated_data)
return bookings
Here are my server logs:
Server Log
------------------------------------------------------------------------
Below is a solution I created to the following problem. Any one can perfect it. Note: It gets help from the function overlapping_bookings() in order to stop anymore bookings for a single date and time once all drivers are allocated. This throws in a ValidationError from overlapping_bookings() on save()
I hope it helps someone
In serializers.py, the function is overriding ModelViewSet create.
def create(self, validated_data):
""" Function to create booking objects \
and allocate drivers automatically. """
validated_data['client'] = self.context['request'].user
""" Variable to save all drivers (querysets) from the driver \
table ordered by the booking date and time. --> maybe there is \
a better way to do it. Avoid using .get()"""
drivers = Driver.objects.filter(active=True).order_by(
'-booking__booking_date', '-booking__booking_time').all()
""" Check whether the drivers querysets (list) exists """
if drivers.exists():
""" For loop to isolate a single query set from list of \
quersets (drivers) """
for drv in drivers:
""" Condition to check for inner join query between \
driver and booking table carefully filtering them using \
booking_date and booking_time. This code is helped by \
the clean() function in models. Which after every active=True \
driver is allocated a booking, raises a ValidationError. \
It is subject to be made better. Trying to find out how it \
will throw a HttpResponse error instead."""
if Booking.objects.select_related('driver').filter(
booking_date=validated_data['booking_date'],
booking_time=validated_data['booking_time'],
).annotate(drv=F('driver__user_ptr')).exists():
continue
try:
return Booking.objects.create(driver=drv, **validated_data)
except Booking.DoesNotExist:
pass

How do I validate a field based on it's initial value in forms.py?

I have a custom self.clean method in forms.py which contains a check on whether the POSTed date values overlap with existing records. In the case where the user edits their record, but does not change the dates, this validation stops the record update. How can I change the validation with If statement or otherwise dependant on if the user has modified the start_date or end_date fields?
I've tried looking at things like self.get_initial() and self.has_changed() which seem to only be applicable in the view function? I'd rather validate in forms.py.
If I could filter out the record currently being edited from my "overlap" validations, that may be the neatest solution.
forms.py:
class EditBookingForm(ModelForm):
class Meta:
model = Bookings
#user not included as that is handled in views
fields = ['start_date', 'end_date', 'type', 'approved', 'members', 'guests']
...
def clean(self):
form_data = self.cleaned_data
#check if dates overlap by more than one day (ie. can start/end on the same day)
overlap_in = Bookings.objects.filter(start_date__lt=form_data['start_date'], end_date__gt=form_data['end_date']).count()
overlap_st = Bookings.objects.filter(start_date__lt=form_data['start_date'], end_date__gt=form_data['start_date']).count()
overlap_end = Bookings.objects.filter(start_date__lt=form_data['end_date'], end_date__gt=form_data['end_date']).count()
overlap_same = Bookings.objects.filter(start_date=form_data['start_date'], end_date=form_data['end_date']).count()
overlap_over = Bookings.objects.filter(end_date__gt=form_data['start_date'], start_date__lt=form_data['end_date']).count()
overlap = overlap_end + overlap_in + overlap_same + overlap_st + overlap_over
if overlap > 0:
self._errors["start_date"] = ["Your dates overlap with an existing booking"]
del form_data["start_date"]
return form_data
views.py:
booking = get_object_or_404(Bookings, pk=booking_id)
if request.method == "POST":
form = EditBookingForm(request.POST)
if form.is_valid():
#some code
#if not POST request
else:
form = EditBookingForm(instance=booking)
context = {
'form': form,
}
return render(request, 'bookings/edit_booking_page.html', context)
A ModelForm that is editing an existing instance will be passed the instance that is being edited when initialized. You should be able to use this pre-edit instance and exclude it's pk from your querysets
bookings = Bookings.objects.all()
if self.instance.pk:
bookings = bookings.exclude(pk=self.instance.pk)
overlap_in = bookings.filter(start_date__lt=form_data['start_date'], end_date__gt=form_data['end_date']).count()
# etc
Your 5 querysets can by simplified to at most 3 so long as you validate that end_date is greater than start_date. It's probably possible to reduce them to 1...
# So long as end_date is greater than start_date then this should be covered by overlap_st
# overlap_in = Bookings.objects.filter(start_date__lt=form_data['start_date'], end_date__gt=form_data['end_date']).count()
overlap_st = Bookings.objects.filter(start_date__lt=form_data['start_date'], end_date__gt=form_data['start_date']).count()
# So long as end_date is greater than start_date then this should be covered by overlap_over
# overlap_end = Bookings.objects.filter(start_date__lt=form_data['end_date'], end_date__gt=form_data['end_date']).count()
overlap_same = Bookings.objects.filter(start_date=form_data['start_date'], end_date=form_data['end_date']).count()
overlap_over = Bookings.objects.filter(end_date__gt=form_data['start_date'], start_date__lt=form_data['end_date']).count()

Django: Accessing parent object in new linline object

I have been combing through the internet for quite some while without finding any solution to this problem.
What I am trying to do...
I have the following models:
class TrackingEventType(models.Model):
required_previous_event = models.ForeignKey(TrackingEventType)
class TrackingEvent(models.Model):
tracking = models.ForeignKey(Tracking)
class Tracking(models.Model):
last_event = models.ForeignKey(TrackingEvent)
Now the main model is Tracking, so my admin for Tracking looks like this:
class TrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
class TrackingAdmin(admin.ModelAdmin):
inlines = [TrackingEventInline]
That's it for the current setup.
Now my quest:
In the TrackingAdmin, when I add new TrackingEvent inlines, I want to limit the options of TrackingEventType to onlye those, that are allowed to follow on the last TrackingEvent of the Tracking. (Tracking.last_event == TrackingEventType.required_previous_event).
For this, I would need to be able to access the related Tracking on the InlineTrackingEvent, to access the last_event and filter the options for TrackingEventType accordingly.
So I found this: Accessing parent model instance from modelform of admin inline, but when I set up TrackingEventInline accordingly:
class MyFormSet(forms.BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
kwargs['parent_object'] = self.instance
print self.instance
return super(MyFormSet, self)._construct_form(i, **kwargs)
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
print kwargs
self.parent_object = kwargs.pop('parent_object')
super(MyForm, self).__init__(*args, **kwargs)
class TrackingEventInline(admin.TabularInline):
form = MyForm
formset = MyFormSet
model = TrackingEvent
extra = 0
I get a KeyError at /admin/.../tracking/2/change/ 'parent_object' from self.parent_object = kwargs.pop('parent_object')
Does anyone know how to solve this? Am I approaching the problem the wrong way? I guess this would be pretty easy in a custom form in the frontend, but I really want to use the admin, because the whole application is built to be used from the admin, and it would be a hell lot of work to build a custom admin interface just because of this problem :)
Ok, so posting on StackOverflow is always helping to get the problem straight. I was able to put together a solution that works for me.
It includes defining my own Form in a outer function, as well as defining two InlineAdmin objects for TrackingEvent (one for update / edit, one just for insert).
Here's the code:
def create_trackingevent_form(tracking):
"""
"""
class TrackingEventForm(forms.ModelForm):
"""
Form for Tracking Event Inline
"""
def clean(self):
"""
May not be needed anymore, since event type choices are limited when creating new event.
"""
next_eventtype = self.cleaned_data['event_type']
tracking = self.cleaned_data['tracking']
# get last event, this also ensures last_event gets updated everytime the change form for TrackingEvent is loaded
last_eventtype = tracking.set_last_event()
if last_eventtype:
last_eventtype = last_eventtype.event_type
pk = self.instance.pk
insert = pk == None
# check if the event is updated or newly created
if insert:
if next_eventtype.required_previous_event == last_eventtype:
pass
else:
raise forms.ValidationError('"{}" requires "{}" as last event, "{}" found. Possible next events: {}'.format(
next_eventtype,
next_eventtype.required_previous_event,
last_eventtype,
'"%s" ' % ', '.join(map(str, [x.name for x in tracking.next_tracking_eventtype_options()]))
)
)
else:
pass
return self.cleaned_data
def __init__(self, *args, **kwargs):
# You can use the outer function's 'tracking' here
self.parent_object = tracking
super(TrackingEventForm, self).__init__(*args, **kwargs)
self.fields['event_type'].queryset = tracking.next_tracking_eventtype_options()
#self.fields['event_type'].limit_choices_to = tracking.next_tracking_eventtype_options()
return TrackingEventForm
class TrackingEventInline(admin.TabularInline):
#form = MyForm
#formset = MyFormSet
model = TrackingEvent
extra = 0
#readonly_fields = ['datetime', 'event_type', 'note']
def has_add_permission(self, request):
return False
class AddTrackingEventInline(admin.TabularInline):
model = TrackingEvent
extra = 0
def has_change_permission(self, request, obj=None):
return False
def queryset(self, request):
return super(AddTrackingEventInline, self).queryset(request).none()
def get_formset(self, request, obj=None, **kwargs):
if obj:
self.form = create_trackingevent_form(obj)
return super(AddTrackingEventInline, self).get_formset(request, obj, **kwargs)
I hope this helps other people with the same problem.. Some credit to the Stack Overflow threads that helped me come up with this:
Prepopulating inlines based on the parent model in the Django Admin
Limit foreign key choices in select in an inline form in admin
https://docs.djangoproject.com/en/1.9/ref/models/instances/#django.db.models.Model.clean_fields
Please do not hesitate to ask questions if you have any

Validate a dynamic select field in Django

I'm using Django 1.4 with Python 2.7 on Ubuntu 12.10.
I have a form where I need to populate a few drop-downs dynamically (using jQuery) but need 2 of them to be required and the 3rd to be optional.
I'm using Tastypie to help with the API to get the options. Basically the first drop-down is populated with industry level codes for schools. Once a code is selected a category drop-down is populated for all categories for that code. Once the category is chosen a subcategory drop-down is populated for all subcategories for that combination of code and category.
I'm able to require the code drop-down (it's not dynamically populated). However, I'm having a tough time getting the category drop-down to be required. There are basically 2 routes I can take - front-end validation or back-end validation. I'm trying to go with back-end validation so I can easily create further validation if needed.
Here is the form:
class SchoolProductForm(forms.ModelForm):
cip_category = forms.ChoiceField(required=True,
choices=(('', '----------'),))
def __init__(self, *args, **kwargs):
super(SchoolProductForm, self).__init__(*args, **kwargs)
self.fields['short_description'].widget = TA_WIDGET
self.fields['salary_info'].widget = TA_WIDGET
self.fields['job_opportunities'].widget = TA_WIDGET
self.fields['related_careers'].widget = TA_WIDGET
self.fields['meta_keywords'].widget = TI_WIDGET
self.fields['meta_description'].widget = TI_WIDGET
self.fields['cip'].queryset = models.CIP.objects.filter(
parent_id__isnull=True)
class Meta:
model = models.SchoolProduct
exclude = ('campus',)
I've tried to override the clean method. I've tried to create a field specific clean method. Neither seem to work.
Variations of the following:
def clean(self):
super(SchoolProductForm, self).clean()
if cip_category in self._errors:
del self._errors['cip_category']
if self.cleaned_data['cip_category'] == '----------':
self._errors['cip_category'] = 'This field is required.'
return self.cleaned_data
This gives an error that there is no cip_category in cleaned_data, which makes sense because it didn't validate.
I've tried variations with the field specific clean:
def clean_cip_category(self):
data = self.cleaned_data['cip_category']
self.fields['cip_category'].choices = data
return data
But get a validation error on the page stating my choice is not one of the available choices.
I've tried to create a dynamic field type (several variations):
class DynamicChoiceField(forms.ChoiceField):
def valid_value(self, value):
return True
class SchoolProductForm(forms.ModelForm):
cip_category = DynamicChoiceField(required=True,
choices=(('', '----------'),))
But it accepts ---------- as a valid option (which I don't want) and causes an error since the ORM tries to match a value of ---------- in the database (which it won't find).
Any ideas?
I was able to solve this with a little overriding of a method in ChoiceField.
I added the field to the form and handled the pre-population with the self.initial:
class SchoolProductForm(forms.ModelForm):
cip_category = common_forms.DynamicChoiceField(
required=True, choices=(('', '----------'),))
def __init__(self, *args, **kwargs):
super(SchoolProductForm, self).__init__(*args, **kwargs)
self.fields['short_description'].widget = TA_WIDGET
self.fields['salary_info'].widget = TA_WIDGET
self.fields['job_opportunities'].widget = TA_WIDGET
self.fields['related_careers'].widget = TA_WIDGET
self.fields['meta_keywords'].widget = TI_WIDGET
self.fields['meta_description'].widget = TI_WIDGET
self.fields['cip'].queryset = models.CIP.objects.filter(
parent_id__isnull=True)
# Get the top parent and pre-populate
if 'cip' in self.initial:
self.initial['cip'] = models.CIP.objects.get(
pk=self.initial['cip']).top_parent()
class Meta:
model = models.SchoolProduct
exclude = ('campus',)
Where DynamicChoiceField is:
class DynamicChoiceField(forms.ChoiceField):
def valid_value(self, value):
return True
Then, in the view I added handling in the form_valid override:
def form_valid(self, form):
self.object = form.save(commit=False)
# Handle the CIP code
self.object.cip_id = self.request.POST.get('cip_subcategory')
if self.object.cip_id == '':
self.object.cip_id = self.request.POST.get('cip_category')
self.object.save()