Am having a challenge i hope you can help me over come.
Am building a django driven application for movie ticket bookings and coming up short on the forms.
When a user clicks on a particular movie, i want to render a page that has a form where the user can choose options for his/her ticket like number of tickets, seat number, date etc.
However, my form returns all movies in the database.
i want to be able to return the ONLY the movie the user has clicked on, seeing that this view already returns a particular movie clicked on by a user. How can i do this?
My current method gives me an exception error 'unicode' object has no attribute 'get'
In my forms.py i have this
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
movie = forms.ModelChoiceField(queryset=Movie.objects.get(slug=args[0]))
and in my views.py i have this
class SpecificMovieTemplateView(TemplateView):
model = Movie
template_name = 'movie.html'
def get_context_data(self, *args, **kwargs):
context = super(SpecificMovieTemplateView, self).get_context_data(**kwargs)
context['movie'] = Movie.objects.get(slug=kwargs['movieslug'])
print 'Movie ID is ==> ' + str(context['movie'].id)
context['form_movie'] = MoviePaymentsForm(kwargs['movieslug'])
return context
in my models.py i have this
class MoviePayments(TimeStampedModel):
uuid_placement = shortuuid.encode(uuid.uuid4())
short_uuid = uuid_placement[:8]
reference_id = models.CharField(max_length=8, blank=True, unique=True,
default="%s" % str(short_uuid))
movie = models.ForeignKey(Movie)
ticket = models.ForeignKey(Ticket)
quantity = models.IntegerField()
date = models.ForeignKey(MovieShowDate)
time = models.ForeignKey(MovieShowTimes)
paid_for = models.BooleanField(default=False, blank=False)
mm_transaction_id = models.CharField(max_length=100, blank=True)
I finally figured it out. Like Bogdan pointed out above, i needed to pass the slug field as an argument in the init method, and use filter on the queryset to return that particular movie like so
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, slug, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
self.fields['movie'].queryset = Movie.objects.filter(slug=slug)
The problem is you are passing the movie_slug as first parameter to the form:
context['form_movie'] = MoviePaymentsForm(kwargs['movieslug']) and first parameter to the form is the data dictionary. Modify the form like this:
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, slug=None, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
movie = forms.ModelChoiceField(queryset=Movie.objects.get(slug=slug))
Or remove the argument from the args list like: slug = args.pop(0)
Related
I have two Models for my Project, 1. Category Model and 2. Course Model
Course Model has a Foreign Key reference with my Category Model as shown below.
class Category(models.Model):
categoryname = models.CharField(max_length=200,null=True,blank=True, default="")
class Courses(models.Model):
coursename = models.CharField(max_length=200,null=True,blank=True, default="")
course_category = models.ForeignKey(Category, related_name="courses", blank=True,null=True,on_delete=models.CASCADE)
logo = models.ImageField(upload_to='courselogos', null=True, blank=True)
Initially I was using HTML form and will be able to save the Course data under a Particular Category to the database as:
def add_course(request):
if request.method == 'POST':
course_name = request.POST.get('coursname')
categoryid = request.POST.get('category_id')
category = Category.object.get(id=category_id)
course_logo = request.FILES.get('logo')
course = Courses(coursename=course_name, course_category=category, logo= course_logo)
course.save()
return redirect('/all_category')
Later I decided to move on using Django Model forms and I tried to implement the code as follows
class AddCourseForm(forms.ModelForm):
class Meta:
model = Courses
fields = ('coursename', 'course_category', 'logo')
widgets = {
'coursename' : forms.TextInput(attrs={'class':'form-control'}),
}
def __init__(self, *args, **kwargs):
category_id = kwargs.pop('category_id',1)
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category']=forms.ModelChoiceField(widget=forms.TextInput(), queryset=Category.objects.filter(id=category_id))
Later in the view I have saved the data as
def add_course(request):
if request.method == 'POST':
addcourse = AddCourseForm(request.POST, request.FILES)
if addcourse.is_valid():
addcourse.save()
return redirect('/all_category')
On my HTML page I am passing the input to the 'course_category' inputfield as 1,2,3....etc as the category_id value
I have rendered the field in the form as
{{form.course_category}}
On Submitting the form when my 'course_category' inputfield has value as 1, it saves the data to the database but when the inputfield value is 2 then it is not even entering to the if condition of addcourse.is_valid() in the view function.
As I'm new the Django I'm not able to find the right way to get the ForeignKey value dynamically save the data in reference to that Category. Also I want to populate the same data back to the form in case of edit.
Please guide, thanks in advance.
After debugging the Code a little bit, I modified the init function in the AddCourseForm class as mentioned below that solved my issue but I am not it is the right way to do this or not
def __init__(self, *args, **kwargs):
category_id = None
for key in args[0:1]:
category_id = args[0].get('course_category')
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category']=forms.ModelChoiceField(widget=forms.TextInput(), queryset=Category.objects.filter(id=category_id))
I don't think doing this should be that difficult, here is how you would set the course_category options in the form normally:
# forms.py
class AddCourseForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
self.course_categories = Category.objects.all()
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category'].queryset = self.course_categories
If you want to set a particular category in the form the you can pass an initial value in your view:
# views.py
def add_course(request, pk):
# note: you can pass the category primary key to your view, you need to
# specify this in your URLs and then your template
course_category = Category.objects.get(pk=pk)
form = AddCourseForm(initial={'course_category': course_category})
If you then want to kill all other options entirely, you can use the initial value to set your filter:
# forms.py
class AddCourseForm(forms.ModelForm):
...
def __init__(self, *args, **kwargs):
super(AddCourseForm, self).__init__(*args, **kwargs)
self.fields['course_category'].queryset = Category.objects.filter(
pk=self.fields['course_category'].initial)
I'm new to programming and my first language/stack is Python and Django. I have figured out how to create a dropdown menu in my Script form that is pointing to a different class "Patient" but I can't figure out how to only show me data that the current user created. I'm confused if I should set this in my models.py, forms.py or in the views.py? Here is what I have that I think should be working but it is not. (Tried setting in the views.py)
Models.py
class Patient(models.Model):
author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,)
patient_name = models.CharField(max_length=40, unique=True)
def __str__(self):
return self.patient_name
class Script(models.Model):
author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE, verbose_name='Primary Patient')
So my patient field is my dropdown and it is looking at the Patient class grabbing the patient name string. I only want patient_name entry's that this user created in the dropdown.
Views.py
class ScriptCreateView(LoginRequiredMixin, CreateView):
model = Script
template_name = 'script_new.html'
success_url = reverse_lazy('script_list')
fields = (
'patient',
'drug_name',
'drug_instructions',
'drug_start_day',
'drug_start_time',
'drug_hours_inbetween',
'drug_num_days_take',
)
#This sets user created fields only??
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author=self.request.user
)
#This sets the author ID in the form
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form
)
Forms.py
class ScriptForm(forms.ModelForm):
class Meta:
model = Script
fields = '__all__'
#This is requiring user login for any of these views??
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if user:
self.fields['patient'].queryset = Patient.objects.filter(author=user)
I'm sure it is my lack of experience here but I thought by setting the function def get_queryset in the view that it would only show me user created data. I have googled a bunch and I really can't find the clear answer on this.
In your views.py file initialize form like this please
<form or form_class> = Form(request.POST, user=request.user)
I had to add the last form.fields query below in the view which filtered items only created by "author" which is what I was looking for:
def get_form(self):
form = super().get_form()
form.fields['drug_start_day'].widget = DatePickerInput()
form.fields['drug_start_time'].widget = TimePickerInput()
form.fields['patient'].queryset = Patient.objects.filter(author=self.request.user)
return form
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.
In the admin panel, I can add Persons to my CompleteClass model. There is a M2M relationship between CompleteClass and Person. But, my form doesn't work as it should. The pub_date will update, and I can save the head_count, but not the ModelMultipleChoiceField (persons) -- it will not save.
models.py
class Person(models.Model):
name = models.CharField(max_length=255)
persona_description = models.CharField(max_length=255)
def __str__(self):
return self.name
class CompleteClass(models.Model):
persons = models.ManyToManyField(Person)
class_name = models.CharField(max_length=255)
class_head_count = models.IntegerField()
class_pub_date = models.DateField()
def __str__(self):
return '%s %s' % (self.class_name, self.class_head_count)
def save_complete_class(self):
self.class_pub_date = timezone.now()
self.save()
class Meta:
ordering = ('class_pub_date',)
Here is views.py:
def class_new(request):
if request.method == "POST":
form = CompleteClassForm(request.POST)
if form.is_valid():
complete_class = form.save(commit=False)
complete_class.class_pub_date = timezone.now()
complete_class.save()
form.save_m2m()
return redirect('class_detail', pk=complete_class.pk)
else:
form = CompleteClassForm()
return render(request, 'app/class_edit.html', {'form': form})
and forms.py
class CompleteClassForm(forms.ModelForm):
class Meta:
model = CompleteClass
fields = ('class_name', 'class_head_count',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(CompleteClassForm, self).__init__(*args, **kwargs)
self.fields['class_persons']=forms.ModelMultipleChoiceField(queryset=Person.objects.all())
I've read through the documentation and used the save_m2m since i've set commit=false.
The POST data contains person data, but it's not being written to the database. I'm stumped. Please help!
Only fields named in the fields tuple are saved to the instance. You don't have your m2m field listed there.
You also define your modelchoicefield with a different name - class_persons instead of persons. In fact, there is no reason to define that field separately at all - you haven't changed any of the attributes from the defaults.
And once you've removed that definition, there is also no reason to override __init__, seeing as you never pass the user parameter nor do you use it anywhere in the form.
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)