I have a form that works perfectly fine
models.py:
class Location(models.Model):
title = models.CharField(max_length=300)
description = models.TextField(null=True, blank=True)
address = models.TextField(null=True, blank=True)
class Review (models.Model):
location = models.ForeignKey(Location)
description = models.TextField(null=True, blank=True)
views.py:
class Create(CreateView):
model = coremodels.Review
template_name = 'location/test.html'
fields = '__all__'
def form_valid(self, form):
form.save()
return HttpResponseRedirect('')
return super(Create, self).form_valid(form)
html:
<form action="" method="post">{% csrf_token %}
{{ form}}
<input type="submit" value="Create" />
</form>
When I open the site I can select a location and give a review over the create button. However, now I am dependent on the prefilled values in the Location class. What if I want that a user can directly create a description as well as a title of the location (I don't want the title to be in class Review) I already tried looking for this in the docs but couldn't find anything. Somewhere I read that I could create two different forms that handle to different things but I'm not sure how to merge that all in the class Create. Is there something like model = coremodels.Review & coremodels.Location and then in the html I could do
{{form.title}}
{{form.description}}
Anyone any ideas or search terms I could look for?
Thanks !
EDIT
Ok thanks to Ruddra and this post , here is the working solution. I had to edit it a little in order to get it working for me,
class SomeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
self.fields['title'] = forms.CharField(label='Title', required = False)
self.fields['description'] = forms.CharField(label='Description', required = False)
self.fields['location'] = forms.ModelChoiceField(queryset= Location.objects.all(), required = False) # This line is for making location not required in form field for input
class Meta:
model = Review
fields = '__all__'
def save(self, commit=True):
"""
It will save location from choice field or inputs of title and description
"""
instance = super(SomeForm, self).save(commit=False)
if instance.location_id:
instance.save()
else:
new_location = Location.objects.create(title=self.cleaned_data['title'])
instance.location = new_location
instance.save()
return instance
and the views.py
class Create(CreateView):
model = coremodels.Review
template_name = 'location/test.html'
form_class = SomeForm
Unfortunately you can't use two models like this, you have to write a form and do the stuffs there. For example:
class SomeForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'] = forms.CharField(label='Title', required = False)
self.fields['description'] = forms.CharField(label='Description', required = False)
self.fields['location'] = forms.ModelChoiceField(queryset= Location.objects.all(), required = False) # This line is for making location not required in form field for input
class Meta:
model = Review
fields = '__all__'
def save(self, commit=True):
"""
It will save location from choice field or inputs of title and description
"""
instance = super().save(commit=False)
if instance.location:
instance.save()
else:
new_location = Location.objects.create(title=self.cleaned_data['title'], description = self.cleaned_data['description']])
instance.location = new_location
instance.save()
return instance
Use it in view:
class Create(CreateView):
model = coremodels.Review
template_name = 'location/test.html'
form = SomeForm
And you have make sure that, location is nullable or not required in form (I have added that to the example)
Related
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
I have the two models, Fillup and Car, and the Fillup model has a Foreign key (for recording times you fill up your car with gas, for example), and in the form to create a new Fillup, I want to limit the dropdown for the Car field to only Cars associated with the current user, but right now it's showing all users cars. I've seen a couple solutions that involve passing the request into the form from the view but I can't figure out how to do it using the Class Based Views I currently have set up. Here's my code:
models.py
class Fillup(models.Model):
username = models.ForeignKey(User,on_delete=models.CASCADE)
date = models.DateField(default=date.today)
price_per_gallon = models.FloatField()
trip_distance = models.FloatField()
gallons = models.FloatField()
car = models.ForeignKey('Car',on_delete=models.CASCADE)
#property
def total_sale(self):
return round(self.price_per_gallon*self.gallons, 2)
#property
def mpg(self):
return round(self.trip_distance/self.gallons, 4)
class Car(models.Model):
username = models.ForeignKey(User,on_delete=models.CASCADE)
name = models.CharField(max_length=25)
make = models.CharField(max_length=25)
model = models.CharField(max_length=25)
model_year = models.IntegerField(choices=MODEL_YEARS)
status = models.BooleanField(choices=STATUS)
def __str__(self):
return self.name
views.py
class FillupListView(ListView):
model = Fillup
context_object_name = 'fillup_list'
ordering = ['-date']
# NOT USING THIS YET
# def get_queryset(self):
# return Fillup.objects.filter(user=self.request.user)
class CarListView(ListView):
model = Car
ordering = ['name']
class NewFillup(LoginRequiredMixin,CreateView):
model = Fillup
fields = ('date', 'price_per_gallon', 'trip_distance', 'gallons', 'car')
redirect_field_name = 'fillup_list'
def form_valid(self, form):
form.instance.username = self.request.user
return super().form_valid(form)
class NewCar(LoginRequiredMixin,CreateView):
model = Car
fields = ('name', 'make', 'model', 'model_year', 'status')
redirect_field_name = 'car_list'
def form_valid(self, form):
form.instance.username = self.request.user
return super().form_valid(form)
forms.py
class FillupForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(FillupForm,self).__init__(*args, **kwargs)
self.fields['car'].queryset = Car.objects.filter(username=user)
class Meta():
model = Fillup
fields = ('date', 'price_per_gallon', 'trip_distance', 'gallons', 'car')
class CarForm(forms.ModelForm):
class Meta():
model = Car
fields = ('name', 'make', 'model', 'model_year', 'status')
The overwriting of the init method in FillupForm was just one of the things I tried to get this to work, adapted from another Stackoverflow answer, but it didn't seem to have any effect. Any advice/examples to get this working would be appreciated! And let me know if I should supply any more pieces of my code
I ended up getting my answer to this from r/djangolearning on Reddit.
I needed to add the following to both of my CreateViews:
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
They also pointed out that I needed to replace the fields=('blah blah blah') on both CreateViews with form_class=forms.Fillup/Car
I hope this helps someone with the same issue as me!
You can do something like this in the init method.
cars = Car.objects.filter(username=user)
self.fields['car'].autocomplete = False
self.fields['car'].queryset = users
Hope this helps.
I was wondering if there is a way that I can alter a model form within the views.py file to create a multiple choice dropdown field for form choices. I want to set each option on the choice field from the results of a queryset.
for example:
I want to from_acct field to have a scroll down option with the following list..
wells fargo
chase
tabz
bank of america
the list of banks are results of a query set
Here is what i have so far in the views.py file.
form = TransferForm()
form.fields['from_acct'].queryset = Accounts.objects.filter(user = currentUser).all()
message = 'please fill out the below form'
parameters = {
'form':form,
'currentUser':currentUser,
'message':message,
}
return render(request, 'tabs/user_balance.html', parameters)
here is the forms.py file
class TransferForm(forms.ModelForm):
class Meta:
model = Transfers
fields = ['from_acct', 'to_acct', 'amount', 'memo']
labels = {
'from_acct':'from',
'to_acct':'to',
}
here is the model.py file
class Transfers(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
from_acct = models.CharField(max_length=150, default='account')
to_acct = models.CharField(max_length=150, default='accont')
amount = models.DecimalField(decimal_places=2, max_digits=9, default=0)
memo = models.CharField(max_length=200, default='memo')
frequency = models.SmallIntegerField(default=1)
status = models.SmallIntegerField(default=1)
create = models.DateTimeField(auto_now_add=True)
You can try to set choices arg for CharField by function.
Like that:
class Transfers(models.Model):
field = models.CharField(max_length=255, choices=result_query())
def result_query(self):
# you can use that with self if u need transfers.pk for querying
return Something.objects.exclude(bank_id__in=[bank.id for bank in self.banks.all())
def result_query():
# or there if not
return Something.objects.filter(any_field__gte=123)
For sure, you can realize any logic in the function, so you can dynamically change options for char field.
UPDATE:
Sure, u haven't pass request into the function.
That should be like that:
view.py:
def my_view(request):
if request.method == 'GET':
form = TransferForm(user=request.user)
...
return something here
forms.py
class TransferForm(ModelForm):
class Meta:
model = Transfer
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(TransferForm, self).__init__(*args, **kwargs)
self.fields['accounts'].choices = Accounts.objects.filter(user = currentUser).all()
I want to get data from some fields in a Foriegnkey that belongs to a particular id. But I can;t figure out how to make it work. I have two models;
class Tick(models.Model):
user=models.ForeignKey(User)
event_name=models.CharField(max_length=100)
ticket_quantity=models.PositiveIntegerField(max_length=50, null=True, blank=True,help_text='Optional, if you have unlimited ticket')
ticket_plan_name_a=models.CharField(max_length=100, null=True, blank=True)
ticket_plan_price_a=models.PositiveIntegerField(max_length=50, null=True, blank=True, verbose_name="Price",help_text='ticket price.')
ticket_plan_name_b=models.CharField(max_length=100, null=True, blank=True)
ticket_plan_price_b=models.PositiveIntegerField(max_length=50, null=True, blank=True, verbose_name="Price1",help_text='ticket price.')
Another Models
class BuyTick(models.Model):
user=models.ForeignKey(User)
tik=models.ForeignKey(Tick)
tiket_qty=models.PositiveIntegerField(max_length=100)
pub_date=models.DateTimeField()
full_name=models.CharField(max_length=100)
def __unicode__(self):
return self.tiket
class BuyTickForm(forms.ModelForm):
tik=forms.ModelChoiceField(queryset=Tick.objects.get(pk=tick_id))
class Meta:
model=BuyTick
After trying the above codes out I got
NameError: name 'tick_id' not defined
How can I get a particular tick_id from models Tick so as to display certain fields in the BuyTick form?
Update: For the views
def purchase_ticket(request, tikid):
if request.method=="POST":
form=BuyTickForm(request.POST)
if form.is_valid():
data=form.cleaned_data
newbuy=BuyTick(
user=request.user,
tik=Ticket.objects.get(pk=tikid),
tiket_qty=data['tiket_qty'],
full_name=data['full_name'],
phone_no=data['phone_no'],
pub_date=datetime.datetime.now())
newbuy.save()
return HttpResponse('Your ticket have been booked')
else:
print form.errors
else:
return render_to_response('buytickform.html',{'BuyTickForm':BuyTickForm,'post':Tick.objects.all().get(id=tikid)},context_instance=RequestContext(request))
Template
<p> Event Name: {{post.event_name}} </p>
<form action="." method="POST">
{% csrf_token %}
{{BuyTickForm.as_p}}
<input type="submit" class="radbutton radorange" value="Confirm Ticket"/>
</form>
First your model could use a couple of tweaks:
class BuyTick(models.Model):
. . .
tiket_qty=models.PositiveIntegerField(default=0, max_length=100)
pub_date=models.DateTimeField(auto_now_add=True)
. . .
def __unicode__(self):
# not sure what you're returning here...
return self.tiket
You need to pass in the id to filter on when you instantiate the form, and you can also pass in the user from the request, which you can set automatically:
class BuyTickForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.tikid = kwargs.pop('tikid')
self.user = kwargs.pop('user')
super(BuyTickForm, self).__init__(*args, **kwargs)
tik=forms.ModelChoiceField(queryset=Tick.objects.filter(id=self.tikid))
class Meta:
model=BuyTick
def save(self, commit=True):
buy_tick = super(BuyTickForm, self).save(commit=False)
buy_tick.user = self.user
if commit:
buy_tick.save()
return buy_tick
You need to filter() the Tick objects instead of using get, otherwise you'll be returning more than one object, which will raise an exception when using .get().
The view could use some love:
def purchase_ticket(request, tikid):
tick = get_object_or_404(Tick, id=tikid)
form = BuyTickForm(request.POST or None, tikid=tikid, user=request.user)
if request.method == 'POST' and form.is_valid():
form.save()
# note that these fields don't exist on your model:
# full_name, phone_no
# return something here... a redirect probably
else:
return render(request, 'buytickform.html',
{'tick': tick, 'form': form})
models.py:
class Tag(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now_add=True)
class Post(models.Model):
user = models.ForeignKey(User)
tag = models.ManyToManyField(Tag)
title = models.CharField(max_length=100)
content = models.TextField()
created = models.DateTimeField(default=datetime.datetime.now)
modified = models.DateTimeField(default=datetime.datetime.now)
def __unicode__(self):
return '%s,%s' % (self.title,self.content)
class PostModelForm(forms.ModelForm):
class Meta:
model = Post
class PostModelFormNormalUser(forms.ModelForm):
class Meta:
model = Post
widgets = { 'tag' : TextInput() }
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None
views.py:
if request.method == 'POST':
form = PostModelFormNormalUser(request.POST)
print form
print form.errors
tagstring = form.data['tag']
splitedtag = tagstring.split()
if form.is_valid():
temp = form.save(commit=False)
temp.user_id = user.id
temp.save()
l = len(splitedtag)
for i in range(l):
obj = Tag(name=splitedtag[i])
obj.save()
post.tag_set.add(obj)
post = Post.objects.get(id=temp.id)
return HttpResponseRedirect('/viewpost/' + str(post.id))
else:
form = PostModelFormNormalUser()
context = {'form':form}
return render_to_response('addpost.html', context, context_instance=RequestContext(request))
Here form.is_valid() is always false because it gets the tag as string from form. But it expects list as form.data['tag'] input. Can anyone tell me how can i fix it?
How can i write a custom widget to solve this?
I don't think you need a custom widget (you still want a TextInput), you want a custom Field. To do this, you should subclass django.forms.Field. Unfortunately the documentation is scant on this topic:
If the built-in Field classes don’t meet your needs, you can easily create custom Field classes. To do this, just create a subclass of django.forms.Field. Its only requirements are that it implement a clean() method and that its init() method accept the core arguments mentioned above (required, label, initial, widget, help_text).
I found this blog post that covers both custom widgets and fields in more depth. The author disagrees with the documentation I quoted above - it's worth reading over.
For your specific situation, you would do something like this (untested):
class MyTagField(forms.Field):
default_error_messages = {
'some_error': _(u'This is a message re: the somr_error!'),
}
def to_python(self, value):
# put code here to coerce 'value' (raw data from your TextInput)
# into the form your code will want (a list of Tag objects, perhaps)
def validate(self, value):
if <not valid for some reason>:
raise ValidationError(self.error_messages['some_error'])
Then in your ModelForm:
class PostModelFormNormalUser(forms.ModelForm):
tag = MyTagField()
class Meta:
model = Post
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None