How to subclass django's generic CreateView with initial data? - django-forms

I'm trying to create a dialog which uses jquery's .load() function to slurp in a rendered django form. The .load function is passed the pk of the "alert" object. Also available in the class functions are things like self.request.user so I can pre-fill those fields, shown below in the Message model (models.py):
class Message(models.Model):
user = models.ForeignKey(User)
alert = models.ForeignKey(Alert)
date = models.DateTimeField()
message = models.TextField()
Subclassing django's CreateView makes it pretty easy to generate a context with an instance of the ModelForm (views.py):
class MessageDialogView(CreateView):
""" show html form fragment """
model = Message
template_name = "message.html"
def get_initial(self):
super(MessageDialogView, self).get_initial()
alert = Alert.objects.get(pk=self.request.POST.get("alert_id"))
user = self.request.user
self.initial = {"alert":alert.id, "user":user.id, "message":"test"}
return self.initial
def post(self, request, *args, **kwargs):
super(MessageDialogView, self).post(request, *args, **kwargs)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(form=form)
return self.render_to_response(context)
The problem here is that self.initial does not get rendered with the form. I have insured that the form is indeed calling get_initial and the form instance has the proper initial data in post, but when the form is rendered in the template message.html it doesn't grab any of the initial data like I would expect. Is there a special trick to get this to work? I've scoured the docs (seems to be lacking examples on generic based class views) and source but I can't see what I'm missing.

get_initial() should just return a dictionary, not be bothered with setting self.initial.
Your method should look something like this:
def get_initial(self):
# Get the initial dictionary from the superclass method
initial = super(YourView, self).get_initial()
# Copy the dictionary so we don't accidentally change a mutable dict
initial = initial.copy()
initial['user'] = self.request.user.pk
# etc...
return initial

you can use like :
from django.shortcuts import HttpResponseRedirect
class PostCreateView(CreateView):
model = Post
fields = ('title', 'slug', 'content', 'category', 'image')
template_name = "create.html"
success_url = '/'
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url())
that's work for me

(Edited because what you're trying does actually work)
I ran into the same problem yesterday, but it's working now – I think I was returning an object instead of a dict in get_initial.
In terms of fixing your problem, I'm a little suspicious of how much you seem to be doing in post() – could you try it with the default (non-overrided) post()?
You could also use pdb (or print statements) to check the value of self.get_form_kwargs make sure that initial is being set.

Related

django - pass request.user to ModelForm

i am having a tricky issue. In my views.py i am passing a form in a DetailView. But i am not using a FormMixin. That has the reason that i only want my template to render the form. In the template i use that form to trigger an UpdateView.
class UpdateDetailView(LoginRequiredMixin, DetailView):
model = Balance
def get_context_data(self, **kwargs):
context = super(UpdateDetailView, self).get_context_data(**kwargs)
context['form'] = ManagerForm
return context
def get_queryset(self, *args, **kwargs):
request = self.request
pk = self.kwargs.get('pk')
select = self.kwargs.get('select')
queryset = Balance.objects.filter(pk=pk).filter(owner = request.user).filter(select = select)
return queryset
class BalanceUpdateView(LoginRequiredMixin, UpdateView):
form_class = ManagerForm
model = Balance
def get_success_url(self):
return reverse('url-year', kwargs={'year': self.object.year, 'select': self.object.select})
So far, so good. The issue is that the form in the UpdateDetailView the ChoiceFields are showing select option of other users which one is not supposed to see. Thus i need to override the queryset of the Modelform. Usually i could use the get_form_kwargs method to pass the request.user to the form. But since the UpdateDetailView is not a FormView it doesnt have that (i know, coz i tried desperately). I also tried context['form'] = ManagerForm(initial = {'user': self.request.user}) in the get_context_data method. But i was unable to access user properly with the __init__ method in the forms.py. I got a key error. If i passed it as arg then i get an attribute error. Does anyone have a solution to that problem? Do i need to use a FormMixin?
Thank you for your replies!

Django: customizing the field types needed for create and retrieve serializers

I currently have the following serializer:
serializers.py
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = SurfInstructorSerializer(many=False)
surfers = SurferSerializer(many=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
def create(self, validated_data):
return SurfGroup(**validated_data)
And the following viewset create method (viewset inherited from viewsets.ViewSet as we need some bespoke customization, extra signals and actions etc):
viewsets.py
# Surf Group Create View:
def create(self, request, format=None):
serializer = SurfGroupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = responses.standardized_json_response(
message='Surf Group Objects Have Been Successfully Created',
data=serializer.data
)
return Response(data=response, status=status.HTTP_201_CREATED, headers=headers)
For the retrieve action, the serializer works well, and we have a nested instructor object in the response. However, I want to perform a create by passing in the instructor uuid attrbiute like (see content in the POST textarea):
Rather than a whole object...I was wondering how we achieve this? Is it best to just have two Serializers, one for performing the create, and one the retrieval?
def create(self, validated_data):
surf_group = SurfGroup(
instructor__uuid=validated_data['instructor'],
)
surf_group.save()
return surf_group
It is good question.
I work with this situations many times and it looks like one option is to have two serializers as you mean: 1 for list/retrieve and 1 for save.
Another option (for me) is to set serializer field input as UUID and output as another serializer data like this:
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = serializers.UUIDField()
surfers = SurferSerializer(many=True, read_only=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
# I use this validate method to transform uuid to object which will
# be bypassed to create method for easly save
def validate_instructor(self, instructor_uuid):
try:
return Instructor.objects.get(uuid=instructor_uuid)
except Instructor.DoesNotExist:
# Remember that you dont need to pass field_key: [errors] to ValidationError
# because validate_FIELD will automatically pass field_key for you !
raise ValidationError(['Instructor with the given uuid does not exist.'])
# Overwrite output data
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['instructor'] = SurfInstructorSerializer(instance=instance.instructor).data
return ret

Combining AJAX with ModelMultipleChoiceField and custom form

I've been banging my head for a while and could not find a similar issue.
I'll go over my code
Model
class RestauranteMenu(models.Model):
restaurante = models.ForeignKey(RestauranteUser)
name = models.CharField(max_length=60, blank=False)
class OpeningHours(models.Model):
...
restaurante = models.ForeignKey(RestauranteUser)
menu = models.ForeignKey(RestauranteMenu, blank=True, null=True)
...
Form
class MenuForm(ModelForm):
'''
View = menus(request)
Template = pages/menus.html
'''
horario = ModelMultipleChoiceField(queryset=OpeningHours.objects.all())
def save(self, restaurante, horario, commit=True):
#Linking relationship Restaurant x RestaurantMenu
menu = super(MenuForm, self).save(commit=False)
menu.restaurante = restaurante
if commit:
menu.save()
#Linking relationship RestaurantMenu x OpeningHours
horario = OpeningHours.objects.filter(id=horario, restaurante = restaurante).first()
if horario:
horario.menu = menu
horario.save()
return menu
class Meta:
model = RestauranteMenu
exclude = ['restaurante']
view
def menus(request):
#verify if its an update.
instance = request.POST.get('instance')
if instance not in [None, '']:
menu = RestauranteMenu.objects.get(id=instance)
form = MenuForm(request.POST or None, instance=menu, initial={'horario': OpeningHours.objects.filter(restaurante=request.user).values_list('id', flat=True)})
else:
form = MenuForm(request.POST or None, initial={'horario': OpeningHours.objects.filter(restaurante=request.user).values_list('id', flat=True)})
if request.POST:
if form.is_valid():
try:
#When saving, we pass a restaurant reference and OpeningHours reference.
form.save(request.user, form.data.get('horario'))
JS
$(document).on("click", "#sendmenuform", function() {
var horariosId = [];
$('#horario :selected').each(function(i, selected) {
horariosId.push($(selected).val());
});
$.ajax({
type: "POST",
url: "../menus/",
data: {
name : $('[name="name"]').val(),
horario : horariosId,
instance : $('#sendmenuform').attr("data-id"),
csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val()
},
success : function(data) {
.... process response...
}
});
What's the issue
Based on my models, I want a Menu to have a ManyToOne relationship with OpeningHours, that means that one Menu can be at different OpeningHours.
When I'm submitting my form (via AJAX), I'm not able to populate the field 'horario' inside the form. When instantiating the form, I'm filling the field with a queryset that will filter by that Restaurant.
On the html, I have a select multiple, so that the restaurant is able to link one Menu to several different OpeningHours object.
I don't know how I'm supposed to process the information sent by the AJAX request to the view, specially this ModelMultipleChoiceField. Do I need to override any of the forms method?
ModelMultipleChoiceField expects model objects and not arbitrary strings. Hence using the values_list query set will only land you into trouble. Your form will not validate.
For your use case, use ChoiceFields. You can populate them with a string by overriding your __init__ method. For example, in your Forms.py
horario = ChoiceField(
choices[],)
def __init__(self, *args, **kwargs):
super(MenuForm, self).__init__(*args, **kwargs)
self.fields['horario'].choices = [(x.id, x.modelfieldtodisplay) for x in OpeningHours.objects.all()]
If you want to do this when you post the form or load it do it inside a view that is triggered by a listener for the event. You'll have to write Javascript to handle this similar to this tutorial but this asks you to use ModelChoiceField which I do not recommend for you because it doesn't work gracefully when you're trying to dynamically populate multiple fields and submit the form, validate it and run some operations on the data.
Instead, I implore you to take a look at Dajax and Dajaxice which takes altogether 5 minutes to set up and handles forms and AJAX effortlessly making your job significantly simpler. I do emphasize though, void using ModelChoiceField for you use case. I learnt that the hard way.

Django 1.3 CreateView, ModelForm and filtering fields by request.user

I am trying to filter a field on a ModelForm. I am subclassing the generic CreateView for my view. I found many references to my problem on the web, but the solutions do not seem to work (for me at least) with Django 1.3's class-based views.
Here are my models:
#models.py
class Subscriber(models.Model):
user = models.ForeignKey(User)
subscriber_list = models.ManyToManyField('SubscriberList')
....
class SubscriberList(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=70)
....
Here is my view:
#views.py
class SubscriberCreateView(AuthCreateView):
model = Subscriber
template_name = "forms/app.html"
form_class = SubscriberForm
success_url = "/app/subscribers/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
return super(SubscriberCreateView, self).form_valid(form)
Here is my original form for adding a Subscriber, with no filter:
#forms.py
class SubscriberForm(ModelForm):
class Meta:
model = Subscriber
exclude = ('user', 'facebook_id', 'twitter_id')
Here is my modified form, attempting to filter, but doesn't work:
#forms.py
class SubscriberForm(ModelForm):
class Meta:
model = Subscriber
exclude = ('user', 'facebook_id', 'twitter_id')
def __init__(self, user, **kwargs):
super(SubscriberForm, self).__init__(**kwargs)
self.fields['subscriber_list'].queryset = SubscriberList.objects.filter(user=user)
If I change this modified form as so:
def __init__(self, user=None, **kwargs)
It works - It brings me NO subscriber lists. But any way I try to pass the request user, I invariably get a a name "request" or name "self" not defined error.
So, how can I modify my code to filter subscriber_list by the request.user, and still use Django 1.3's CreateView.
I see you've been posting this question in various places.. and the way I found that is because I was trying to figure out the same thing. I think I just got it working, and here's what I did. I overwrote get_form() from FormMixin to filter a specific form fields queryset:
class MyCreateView(CreateView):
def get_form(self, form_class):
form = super(MyCreateView,self).get_form(form_class) #instantiate using parent
form.fields['my_list'].queryset = MyObject.objects.filter(user=self.request.user)
return form

Django Ajax field help

I have a Django application where I'm trying to have a form that populates a drop down dynamically based on a previous dropdown.
simplified Models:
class VehicleMake(models.Model):
make = models.CharField(max_length = 30)
class VehicleModel(models.Model):
model = models.CharField(max_length = 80)
make = models.ForeignKey(VehicleMake)
class Listing(models.Model):
make = models.ForeignKey(VehicleMake)
model = models.ForeignKey(VehicleModel)
Form:
class DynamicChoiceField(ModelChoiceField):
def clean(self, value):
return value
class MyForm(ModelForm):
category = ModelChoiceField(VehicleCategory.objects, widget=forms.Select(attrs={'onchange':'FilterMakes();'}))
make = DynamicChoiceField(VehicleMake.objects,widget=forms.Select(attrs={'disabled':'true','onchange':'FilterModels();'}), empty_label="Select Model")
model = DynamicChoiceField(VehicleModel.objects,widget=forms.Select(attrs={'disabled':'true'}), empty_label="Select Make")
class Meta:
model = Listing
View:
def new_listing(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
form.save()
else:
form = MyForm()
return render_to_response("newlisting.html", {
"form": form,'model_id':model_id,'make_id':make_id
})
I also have some ajax defined for the auto-populate but this is not the problem
When I submit the form I get the following:
Cannot assign "u'2'": "Listing.make" must be a "VehicleMake" instance.
if I try
make=VehicleMake.objects.get(pk=request.POST['make'])
form.fields['make'] = make
then I get
'VehicleMake' object has no attribute 'widget'
After the suggestion of one of the commenter's that the DynamicChoiceField class was the culprit I removed it and set the form objects for ModelChoiceFields with the exact same other parameters. The object appears to pass and validate correctly as well. The extra class existed based on an old tutorial I found. it appears that what the author did there works with the forms.ChoiceField but is not required for using a ModelChoiceField
thanks everyone for the help

Resources