I have the following models:
class Project(models.Model):
title = models.CharField(max_length="100")
pub_date = models.DateField(auto_now_add=True, editable=False)
budget = models.IntegerField()
class Milestone(models.Model):
title = models.CharField(max_length="50")
budget_percentage = models.IntegerField(max_length=2)
project = models.ForeignKey(Project)
In the creation form for a Project, i've included an inline formset for milestones.
I want to validate that when a Project is subbmitted, at least 4 milestones are created, also that the budget_percentage of all milestones sums up to 100
This is my form:
class BaseMilestoneProjectFormSet(BaseFormSet):
def clean(self):
if any(self.errors):
# Don't bother validating the forms unless each form is valid on its own
return
if len(self.forms) < REQUIRED_MILESTONES:
raise forms.ValidationError("At least %s milestones need to be created" % REQUIRED_MILESTONES)
# Set initial control variables
# Total percentage of budget to control
total_percentage = 0
# Date to control that milestones are linear, i.e. that second milestone isn't delivered before first
current_control_date = date.min
for i, form in zip(range(len(self.forms)), self.forms):
if i == 0 and form.budget_percentage > MAX_BUDGET_FIRST_MILESTONE:
raise forms.ValidationError("First milestone budget must not exceed %s percentage" % MAX_BUDGET_FIRST_MILESTONE)
elif form.budget_percentage > MAX_BUDGET_MILESTONE:
raise forms.ValidationError("Milestone's budget must not exceed %s percentage" % MAX_BUDGET_MILESTONE)
if form.estimated_delivery_date < current_control_date:
raise forms.ValidationError("Milestones must be linear, check your delivery dates")
# Set control variables for next iteration
current_control_date = form.estimated_delivery_date
total_percentage += form.budget_percentage
if total_percentage != 100:
raise forms.ValidationError("All milestones budget percentage should sum up to 100%")
When I submit the form with 3 empty milestones forms it doesn't do nothing about the first
forms.ValidationError(...)
I've verified with raise Exception() that in fact it enters the if, but it continues as if nothing happens.
It's an error in my code or a misunderstood concept of ValidationError?
Any help would be greatly appreciated, thanks in advance.
Edit:
I'm adding the view code that was missing
class DelayedModelFormMixin(ModelFormMixin):
def form_valid(self, form):
self.object = form.save(commit=False)
self.prepare_object_for_save(self.object)
self.object.save()
if hasattr(self.object, "save_m2m"):
self.object.save_m2m()
return super(ModelFormMixin, self).form_valid(form)
def prepare_object_for_save(self, obj):
pass
class ProjectNew(CreateView, DelayedModelFormMixin):
model = Project
success_url = '/projects/project/%(slug)s'
form_class = ProjectForm
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(ProjectNew, self).dispatch(request, *args, **kwargs)
def prepare_object_for_save(self, obj):
obj.owner = self.request.user
# Code for stacked milestones and rewards
context = self.get_context_data()
milestone_form = context['milestone_formset']
reward_form = context['reward_formset']
if milestone_form.is_valid() and reward_form.is_valid():
self.object = form.save()
milestone_form.instance = self.object
milestone_form.save()
reward_form.instance = self.object
reward_form.save()
return HttpResponseRedirect(success_url)
def form_invalid(self, form):
return self.render_to_response(self.get_context_data(form=form))
def get_context_data(self, **kwargs):
context = super(ProjectNew, self).get_context_data(**kwargs)
if self.request.POST:
context['milestone_formset'] = MilestoneFormSet(self.request.POST)
context['reward_formset'] = RewardFormSet(self.request.POST)
else:
context['milestone_formset'] = MilestoneFormSet()
context['reward_formset'] = RewardFormSet()
return context
If i understand your question the problem is that you are counting how many forms are inside the formset, but you want/need to count how many BOUND forms are in the formset.
So, i think you need to change this line:
if len(self.forms) < REQUIRED_MILESTONES:
for these lines:
bounded_forms = filter(lambda form:form.isbound(), self.forms)
if len(bounded_forms) < REQUIRED_MILESTONES:
then you can raise the validation error as you are doing it.
Edit:
Maybe you are not understanding how formset validation works. May be this can help, specially this phrase:
The formset clean method is called after all the Form.clean methods have been called. The errors will be found using the non_form_errors() method on the formset.
If i understand what you mean when you say
When I submit the form with 3 empty milestones forms it doesn't do nothing about the first forms.ValidationError(...)
then, you are not seeing the raised errors. So, the place to look for them is the formset "non_form_errors" method. Put something like this in your template:
<span>{{milestone_formset.non_form_errors}}</span>
Now you should see the errors you are raising in milestone formset clean method.
Hope it helps!
Related
I can't seem to figure out how to add my graph to a Class Detail View? Is it not possible to do so? I add it to the detailView, and call it in my template with:
{{ div | safe }}
But it does not show? I've gotten it to work perfectly fine in a view and template separately.
Here's the whole detailview I'm trying to implement it into.
DetailView
class MedarbejderDetailView(FormMixin, DetailView):
template_name = 'evalsys/medarbejder/detail.html'
model = Medarbejder
form_class = OpretEvalForm
def evalgraph(self):
colors = ["#40e862", "#ff9d26", "#ff1424"]
over = 0
møder = 0
under = 0
none = 0
counts = []
items = ["Overstiger forventning", "Møder forventning", "Under forventning", "Ingen bedømmelse"]
eval_vudering = Evaluering.objects.values("vuderingsnavn__vuderingsnavn")
source = ColumnDataSource(data=dict(items=items, counts=counts))
for i in eval_vudering:
if "Overstiger forventning" in i.values():
over += 1
elif "Møder forventning" in i.values():
møder += 1
elif "Under forventning" in i.values():
under += 1
elif None in i.values():
none += 1
counts.extend([over, møder, under, none])
plot = figure(x_range=items, plot_height=500, plot_width=500, title="Opsumering af evalueringer",
toolbar_location=None, tools="pan, wheel_zoom, box_zoom, reset, tap", tooltips="#items: #counts")
plot.title.text_font_size = "20pt"
plot.vbar(x="items", top="counts", width=0.9, source=source, legend="items", line_color='black',
fill_color=factor_cmap("items", palette=colors, factors=items))
plot.legend.label_text_font_size = "13pt"
script, div = components(plot)
return render(self, 'evalsys/medarbejder/detail.html', {'script': script, 'div': div})
def view_medarbejder_with_pk(self, pk=None):
if pk:
medarbejder = Medarbejder.objects.get(pk=pk)
else:
medarbejder = self.medarbejder
args = {'medarbejder': medarbejder}
return render(self, 'evalsys/medarbejder/detail.html', args)
def get_context_data(self, **kwargs):
context = super(MedarbejderDetailView, self).get_context_data(**kwargs)
context['eval_list'] = Evaluering.objects.all()
context['fag_list'] = Fag.objects.all()
context['ma'] = Medarbejder.objects.get(pk=self.kwargs.get('pk'))
context['instruktør'] = User.objects.get(username=self.request.user)
return context
def post(self, request, pk):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
print(form.cleaned_data)
instance = form.save(commit=False)
instance.instruktør = request.user
instance.ma = self.object
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
item = form.save()
self.pk = item.pk
return super(MedarbejderDetailView, self).form_valid(form)
def form_invalid(self, form):
return super(MedarbejderDetailView, self).form_invalid(form)
def get_success_url(self):
return reverse_lazy("evalsys:view_evaluering_with_pk", kwargs={'pk': self.pk})
URLs
path('se_alle_evalueringer/<int:pk>', views.MedarbejderEvalDetailView.as_view(), name="view_evaluering_with_fag"),
I know I'm calling the function "view_evaluering_with_fag", so it is because I'm not calling my Bokeh function "evalgraph"?
Didn't know folks couldn't use this as a way to ask follow up questions when we're researching the same question.
Anyhow, I am so about to make your day! With A LOT of trial and error (I've only been coding with Python and Django for a month with no real coding background except Java back in 1999) I got bokeh to render from detailview. It appears the trick is to get stuff under the get_context_data function. I don't know how I came to this conclusion, but I figured the script and div context weren't making their way to the render so I was trying to get them into context. As you'll see below, I put script and div as context['script']=script and context['div']=div. My situation looks a bit simpler, I'm just parsing a bike data file and plotting the data, but hopefully this gets you on your way if you're still trying to make this work.
class FitFileDetailView(DetailView):
model = FitFiles
def get_context_data(self, **kwargs):
model = FitFiles
ff = FitFiles.objects.get(pk=self.kwargs.get('pk'))
ffile = ff.fitfiles.path
fitfile2 = FitFile(ffile)
while True:
try:
fitfile2.messages
break
except KeyError:
continue
workout2 = []
for record in fitfile2.get_messages('record'):
r2 = {}
# Go through all the data entries in this record
for record_data in record:
r2[record_data.name] = record_data.value
workout2.append(r2)
df2 = pd.DataFrame(workout2)
df2['time']=(df2['timestamp'] - df2['timestamp'].iloc[0]).astype('timedelta64[s]')
#Bokeh code
df2 = pd.DataFrame(workout2)
df2['time']=(df2['timestamp'] - df2['timestamp'].iloc[0]).astype('timedelta64[s]')
p2 = figure(x_axis_label='time', y_axis_label='watts', tools="", plot_width=1000, plot_height=500)
p2.line(df2['time'], df2['power'])
p2.line(df2['time'], df2['heart_rate'], color='red')
script, div = components(p2)
context = super(FitFileDetailView, self).get_context_data(**kwargs)
context['script']=script
context['div']=div
return context
THE CONTEXT
I am trying to implement a tagging system for my project. The various plug-in solutions (taggit, tagulous) are each unsuitable in some way.
I would like to allow users to select from existing tags or create new ones in a Select2 tagging field. Existing tags can be added or removed without problem. My difficulty is in the dynamic generation and assignment of new tags.
MY APPROACH
Select2 helpfully renders the manually-entered tags differently in the DOM from those picked from the database via autocomplete. So upon clicking submit, I have javascript collect the new tags and string them together in the value of a hidden input, then delete them from the Select2 field to avoid any validation errors (the form otherwise POSTs the tag names as the ids, which throws a db error).
In the view, I iterate over the desired new tags. For each entry I create the new tag, then add it to the parent object's related set.
THE PROBLEM
While this successfully creates each tag (verified via Admin) it doesn't add it to the related set.
No errors are generated on the (clearly not succeeding) related set add.
The newly-generated tags are correctly instantiated and can be Select2-chosen and sucessfully assigned on a subsequent UpdateView, so I'm certain the problem lies in the view-assignment of the tags to the parent.
The same code executed via the Django shell work flawlessly, so I don't believe its a simple syntax error.
Thus the locus of the problem seems to be in the POST view code adding newly-generated tags to the parent, but I cannot see where the code goes astray.
Thanks for any insights or advice!
models.py:
class Recipe_tag(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
tag = models.CharField('Tag name',max_length=32,null=False,unique=True)
def __str__(self):
return str(self.tag)
class Recipe_base(models.Model):
id = models.UUIDField(primary_key=True,default=uuid.uuid4,null=False)
name = models.CharField('Recipe name',max_length=128,null=False)
tags = models.ManyToManyField(Recipe_tag,related_name='recipes',null=True,blank=True)
def __str__(self):
return str(self.name)
The post portion of the view:
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateTagsForm(request.POST,instance=r)
form_valid = form.is_valid()
if form_valid:
if form.has_changed:
f = form.save(commit=False)
clean = form.cleaned_data
f.addedTags = clean['addedTags']
if f.addedTags == 'placeholder':
pass
else:
new_tags = f.addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
r.tags.add(a)
f.save()
form.save_m2m()
else:
pass
return self.form_valid(form)
else:
return self.form_invalid(form)
Doing further digging, I found a post on another site in which the OP was experiencing the same issues. The trick is to remove the m2m assignment from the "create" or "update" process entirely, because the final save() that occurs in form_valid will discard any changes to the parent's related set.
In my case, the solution was to punt the iteration/assignment of the new tags to form_valid, directly after the final save() occurs there:
def form_valid(self, form, **kwargs):
self.object = form.save()
addedTags = kwargs['addedTags']
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
if addedTags == 'placeholder':
pass
else:
new_tags = addedTags.split(',')
for new_tag in new_tags:
a = Recipe_tag(tag=new_tag)
a.save()
# print("debug // new tag: %s, %s" % (a.tag, a.id))
r.tags.add(a)
# print("debug // added %s to %s" % (a.tag,r.id))
return HttpResponseRedirect(self.get_success_url())
def get(self, request, *args, **kwargs):
self.object = Recipe_base.objects.get(id=self.kwargs.get('pk'))
recipe_name = self.object.name
recipe_id = self.kwargs.get('pk')
form = RecipeUpdateForm(instance=Recipe_base.objects.get(id=self.kwargs.get('pk')))
return self.render_to_response(self.get_context_data(form=form,recipe_name=recipe_name,recipe_id=recipe_id))
def post(self, request, *args, **kwargs):
self.object = None
r = Recipe_base.objects.get(id=self.kwargs.get('pk'))
form = RecipeUpdateForm(request.POST,instance=r)
form_valid = form.is_valid()
#print("debug // form_valid PASSED")
if form_valid:
if form.has_changed:
#print("debug // Form changed...")
f = form.save(commit=False)
#print("debug // Passes save C=F")
clean = form.cleaned_data
#print('debug // pre-existing tags: ',clean['tags'])
f.addedTags = clean['addedTags']
addedTags = clean['addedTags']
#print('debug // manual tags: ',clean['addedTags'])
f.save()
form.save_m2m()
#print("debug // Form saved")
else:
#print("debug // Form unchanged, skipping...")
pass
#print("debug // Reached successful return")
return self.form_valid(form, addedTags=addedTags,pk=r.id)
else:
#print("debug // Form fails validation")
#print("debug // Reached unsuccessful return")
return self.form_invalid(form)
I want to offer users the possibility to create a new publication based on an existing publication. To do that, I want them to click a link to "basedview" that contains the id of the publication they want the new item to base on. There are two formsets for n:n relations included.
that should open a prefilled form with all fields prefield with the data from the publication it's based on. once the user has made changes as needed, it should then save a new publication and new relations for the fieldset - the latter being the difficult part of it.
So my question is - how can I load all corresponding formsets from the database and then delete all their pk but still keep the relation to the publication item?
Right now it is like this in the get method:
self.object = None
try:
self.object = KombiPublikation.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404("Keinen Output unter dieser PubID gefunden.")
form = KombiPublikationForm(instance=self.object)
pubspr_formset = KombiPublikationSpracheFormset(instance=self.object)
pubpers_formset = KombiPublikationPersonFormset(instance=self.object)
But that ends up to be just an edit of the existing publication. I somehow have to delete the pk after I populated the formset or find a way to populate the formset differently. Any Ideas?
Thank you very much!
Here the full code excerpt:
class PublikationBasedView(PublikationCreateView):
def get(self, request, *args, **kwargs):
self.object = None
try:
self.object = KombiPublikation.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404("Keinen Output unter dieser PubID gefunden.")
#todo: delete the pk of all objects in forms in formset, else they stay the same and are also changed!!
#fix: delete pk in objekt in order to save it as a new objekt - else based does not work at all!
#self.object.pk=None
form = KombiPublikationForm(instance=self.object)
pubspr_formset = KombiPublikationSpracheFormset(instance=self.object)
pubpers_formset = KombiPublikationPersonFormset(instance=self.object)
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset,
)
)
#its based on this create view
class PublikationCreateView(LoginRequiredMixin, ShowNumberOfItems, CreateView):
form_class = KombiPublikationForm
template_name = 'output/pub_create.html'
model = KombiPublikation
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
pubspr_formset = KombiPublikationSpracheFormset()
pubpers_formset = KombiPublikationPersonFormset()
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset
)
)
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
pubspr_formset = KombiPublikationSpracheFormset(self.request.POST)
pubpers_formset = KombiPublikationPersonFormset(self.request.POST)
if form.is_valid() and pubspr_formset.is_valid() and pubpers_formset.is_valid():
return self.form_valid(form, pubspr_formset, pubpers_formset)
else:
return self.form_invalid(form, pubspr_formset, pubpers_formset)
def get_success_msg(self):
return 'Ihr Output wurde erfolgreich unter PubID {} angelegt. Speicherort: {}. <br>'.format(self.object.pk, self.object.status)
def form_valid(self, form, pubspr_formset, pubpers_formset):
""" Called if all forms are valid."""
self.object = form.save()
pubspr_formset.instance = self.object
pubspr_formset.save()
pubpers_formset.instance = self.object
pubpers_formset.save()
messages.success(self.request, self.get_success_msg())
return redirect(self.get_success_url())
def form_invalid(self, form, pubspr_formset, pubpers_formset):
""" Called if whether a form is invalid. Re-renders data-filled forms and errors."""
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset,
))
Once you've set the Form's instance it's bound to that object. All updates will be to the object you passed.
Instead you need to set the Form's initial value
Use initial to declare the initial value of form fields at runtime. For example, you might want to fill in a username field with the username of the current session.
Then there's a utility at django.forms.models.model_to_dict that'll give you the dict you need for initial:
Returns a dict containing the data in instance suitable for passing as a Form's initial keyword argument.
So you'll need to do something like this:
from django.forms.models import model_to_dict
object = # Your code here...
# You don't want `id`. Possibly others...?
initial_data = model_to_dict(object, exclude=['id'])
form = YourFormClass(initial=initial_data)
# ...
Hopefully that helps.
I solved the problem and since it was a bit more complicated then expected, I share my finding here - if someone finds a simpler solution feel free to add another comment
That is the final get method in the view:
def get(self, request, *args, **kwargs):
self.object = None
try:
self.object = KombiPublikation.objects.get(pk=self.kwargs['pk'])
except ObjectDoesNotExist:
raise Http404("Keinen Output unter dieser PubID gefunden.")
#load all form initials and render the form correctly - but save new objects
#1. make sure the main publikation object is saved as a new object:
self.object.pk = None
self.object.erstellungsdatum = datetime.now()
form = KombiPublikationForm(instance=self.object)
#2. get the corresponding querysets for sprache and person:
pubspr = KombiPublikationSprache.objects.filter(publikation=self.kwargs['pk'])
pubpers = KombiPublikationPerson.objects.filter(publikation=self.kwargs['pk'])
#make a list of dicts out of the querysets and delete pk id and fk relations
pubspr_listofdicts = []
for pubspr in pubspr:
pubspr_dict= model_to_dict(pubspr)
del pubspr_dict['id']
del pubspr_dict['publikation']
pubspr_listofdicts.append(pubspr_dict)
pubpers_listofdicts = []
for pubpers in pubpers:
pubpers_dict=model_to_dict(pubpers)
del pubpers_dict['id']
del pubpers_dict['publikation']
pubpers_listofdicts.append(pubpers_dict)
#create new formsets with the right amount of forms (leng(obj_listofdicts)
KombiPublikationSpracheFormset = inlineformset_factory(KombiPublikation,
KombiPublikationSprache,
form=KombiPublikationSpracheForm,
extra=len(pubspr_listofdicts),
can_delete=True,
can_order=True,
min_num=1,
validate_min=True)
KombiPublikationPersonFormset = inlineformset_factory(
KombiPublikation,
KombiPublikationPerson,
form=KombiPublikationPersonForm,
extra=len(pubpers_listofdicts),
can_delete=True,
can_order=True,
min_num=0,
validate_min=True)
#initiate the formset with initial data:
pubspr_formset = KombiPublikationSpracheFormset(instance=self.object, initial=pubspr_listofdicts)
pubpers_formset = KombiPublikationPersonFormset(instance=self.object, initial=pubpers_listofdicts)
return self.render_to_response(
self.get_context_data(
form=form,
pubspr_formset=pubspr_formset,
pubpers_formset=pubpers_formset,
)
)
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
I've wrote a form called AnswerForm. This form is used to get an answer from the choices of the question.(Choice and Question are models). Here is my code to AnswerForm:
class AnswerForm(forms.Form):
question = forms.ModelChoiceField(queryset=Question.objects.all(),
required=True,
widget=forms.HiddenInput(attrs={"class": "hidden-input"}))
def __init__(self, *args, **kwargs):
super(AnswerForm, self).__init__(*args, **kwargs)
self.fields['answer'] = forms.ModelChoiceField(queryset=self.initial["question"].choice_set.all(),
required=False,
widget=ExamChoiceInput)
def clean_question(self):
return self.initial["question"]
I use this form in a formset so user can answer many questions at one time. However when the users sends the form a KeyError value is raised for self.initial["question"] in second line of init. (I've printed self.initial and it's completely empty). Here is the function in which I process the request with post method:
def process_saving_form(request):
if request.method == "POST":
current_user = request.user
AnswerFormSet = formset_factory(AnswerForm)
formset = AnswerFormSet(request.POST)
if formset.is_valid():
for form in formset:
new_user_madechoice = MadeChoice(
user=current_user,
choice=form.cleaned_data["answer"]
)
try:
current_user_madechoice = current_user.madechoice_set.get(choice__question=form.cleaned_data["question"])
if current_user_madechoice.choice != form.cleaned_data["answer"]:
current_user_madechoice.delete()
new_user_madechoice.save()
except MadeChoice.DoesNotExist:
new_user_madechoice.save()
May you tell me what's the problem?
The problem is that you did not pass any initial data to you AnswerFormSet.
See how I did it in the answer to your previous question:
initial = [{'question': q} for q in questions]
formset = AnswerFormSet(request.POST, initial=initial)