auth select chooses in forms.py - python

I have read this code in this question and look nice.
but if I have user auth and I want user select only your odjects how to change that code ?for ex chooses your personal upload images.
from django.forms.widgets import Select
class ProvinceForm(ModelForm):
class Meta:
CHOICES = Province.objects.all()
model = Province
fields = ('name',)
widgets = {
'name': Select(choices=( (x.id, x.name) for x in CHOICES )),
}
my model :
class MyModel(models.Model):
user = models.ForeignKey(User, unique=True)
upload = models.ImageField(upload_to='images')

Whenever you instantiate your form inside your view, you should pass the user object, like this my_form = MyModelForm(user=request.user).
Then build your MyModelForm:
# forms.py
from django.forms import ModelForm
from django.forms.widgets import Select
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
# extract "user" value from kwrags (passed through form init). If there's no "user" keyword, just set self.user to an empty string.
self.user = kwargs.pop('user', '')
super(MyModelForm, self).__init__(*args, **kwargs)
if self.user:
# generate the choices as (value, display). Display is the one that'll be shown to user, value is the one that'll be sent upon submitting (the "value" attribute of <option>)
choices = MyModel.objects.filter(user=self.user).values_list('id', 'upload')
self.fields['upload'].widget = Select(choices=choices)
class Meta:
model = MyModel
fields = ('upload',)
Now, whenever you instantiate the form with a user keyword argument (my_form = MyModelForm(user=request.user)), this form will be rendered like this (in your template write it like {{ my_form }}):
<select>
<option value="the_id_of_the_MyModel_model">upload_name</option>
<option value="the_id_of_the_MyModel_model">upload_name</option>
...
</select>
Finally, in order to display images in the dropdown menu (remember, "value" is the one that will be sent back to server upon submiting the form, while the display one just for the UX), take a look here.
[UPDATE]: How to do it in your views.py
# views.py
def my_view(request):
my_form = MyModelForm(user=request.user)
if request.method == 'POST':
my_form = MyModelForm(request.POST, user=request.user)
if my_form.is_valid():
# ['upload'] should be the name of the <select> here, i.e if <select name="whatever"> then this should be "whatever"
pk = my_form.cleaned_data['upload']
# image, now, is the value of the option selected (that is, the id of the object)
obj = MyModel.objects.get(id=pk)
print(obj.upload.url) # this should print the image's path
return render(request, 'path/to/template.html', {'my_form': my_form})

Related

Error while saving foreign key data in database using Django Model Form

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)

Manually set model fields in ModelForm

I have a model with a foreign key and a unique constraint as follows:
class Menu(models.Model):
tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE)
name = models.CharField(max_length=128)
date_menu = models.DateField()
class Meta:
constraints = [
models.UniqueConstraint(fields=['tournament', 'name', 'date_menu'], name="unique_name_menu")
]
I would like to create a form to add instance of Menu. However the value of tournament is set by the URL of the page. I do not want the user to be able to set it.
For this I use a modelForm, excluding the tournament field :
class MenuForm(forms.ModelForm):
date_menu = forms.DateField(initial=datetime.datetime.now())
class Meta:
model = Menu
exclude = ['tournament']
Here is my view :
def add_menu(request, tournament_slug):
tournament = get_object_or_404(Tournament, slug=tournament_slug)
form = MenuForm(request.POST or None)
if form.is_valid():
menu_id = form.save(commit=False)
menu_id.tournament = Tournament.objects.get(pk=1)
menu_id.save() # I get the integrity error only here
return HttpResponseRedirect(reverse('admin'))
return render(request, "view.html", {'form': form, 'formset': formset, "tournament": tournament})
My problem is that when I call the .is_valid() function on this form the uniqueness condition cannot be checked as the tournament field is not set. As a result I get an integrity error when calling the save function in the view.
The question is : how can link the Menu instance created by the form to add the tournament field before checking if it's valid? If it's not the right way of doing it, how can I check the uniqueness of the model instance and return the corresponding errors to the template when needed?
I tried including the tournament field as hidden field in the view, it works but I don't know if that's the best way of doing it...
You should simply instantiate the form with an unsaved instance of Menu so your view should be like:
def add_menu(request, tournament_slug):
tournament = get_object_or_404(Tournament, slug=tournament_slug)
if request.method == 'POST':
form = MenuForm(request.POST, instance=Menu(tournament=tournament))
if form.is_valid():
menu_id = form.save()
return HttpResponseRedirect(reverse('admin'))
else:
form = MenuForm(instance=Menu(tournament=tournament))
return render(request, "view.html", {'form': form, "tournament": tournament})
Also the form calls _get_validation_exclusions() and excludes fields not present in the form from validation. You can try to override validate_unique to overcome this:
class MenuForm(forms.ModelForm):
date_menu = forms.DateField(initial=datetime.datetime.now())
class Meta:
model = Menu
exclude = ['tournament']
def validate_unique(self):
exclude = self._get_validation_exclusions()
if 'tournament' in exclude:
exclude.remove('tournament') # Make sure `tournament` gets validated
try:
self.instance.validate_unique(exclude=exclude)
except ValidationError as e:
self._update_errors(e)
Note: I changed your view structure to avoid using MenuForm(request.POST or None) which is an antipattern. (Forms
can be valid even if nothing is sent in the POST data, with the way
you write such forms would be considered invalid).
Edit: As discussed in the comments perhaps the option of a hidden and disabled field is much better than overriding the forms validate_unique method:
class MenuForm(forms.ModelForm):
tournament = forms.ModelChoiceField(
queryset=Tournament.objects.all(),
widget=forms.HiddenInput(),
disabled=True
)
date_menu = forms.DateField(initial=datetime.datetime.now())
class Meta:
model = Menu
fields = ['tournament', 'name', 'date_menu']

Django ModelForm not displaying all fields

I'm trying to build forms linked to a PostgreSQL database using Django ModelForms. The template is rendering two of the fields(the ones with ManyToMany relationships), but it only gives me an empty box for "title".
This is my forms.py:
Forms.py:
class ProgramForm(forms.ModelForm):
class Meta:
model = Program
fields = ['function','task', 'title']
widgets = {
'function' : forms.Select,
'task' : forms.Select,
'title' : forms.Select,
}
This is my Models.py:
class Program(models.Model):
title = models.CharField(max_length=255)
function = models.ManyToManyField(function, related_name='programs')
task = models.ManyToManyField(Task, related_name='programs')
def __unicode__(self):
return self.title
class Task(models.Model):
tasknum = models.CharField(max_length=20)
taskname = models.CharField(max_length=100)
task_num_name = models.CharField(max_length=100)
function = models.ForeignKey(Function, related_name="tasks")
def __unicode__(self):
return self.task_num_name
class Function(models.Model):
function = models.CharField(max_length=50)
function_abrev = models.CharField(max_length = 25)
def __unicode__(self):
return self.function
Views.py:
def main(request):
return render (request, 'assignments/main.html')
def add_program(request):
form = ProgramForm()
return render (request, 'assignments/ad_form.html', {"form":form})
def link(request):
if request.method == 'POST':
form = ProgramForm(request.POST)
if form.is_valid():
return HttpResponse("we maybe getting somewhere")
else:
return HttpResponse("keep working")
I need a couple of things to happen:
I need for the "title" to render in the html page as a scroll down(the same way "function" and "task" appear.
I need to be able to save the relationships. The models are populated with all the information required with the exception of the relationships. The objective is for a staff member to be able to chose a "function", for that choice to act as a filter for the "task" scroll down(function and task have a OneToMany), and then allow them to choose any programs they want to add to their portfolio.
Any help will be much appreciated.
1. Title field in form
For this, I don't quite understand how the title field could be a scroll down the same way function and task are. Function and task are drop downs because they are manytomany fields linked to other models, meaning that the user has to pick which other objects in the Functions model and the Tasks model are to be linked. The title field, on the other hand, is just a CharField and so there is no defined set of things for the user to pick from. To allow the user to enter in the title for the Program, you should change the widget for title to Textarea() as such:
forms.py
from django.forms import ModelForm, Textarea
class ProgramForm(forms.ModelForm):
class Meta:
model = Program
fields = ['function','task', 'title']
widgets = {
'function' : forms.Select,
'task' : forms.Select,
'title' : Textarea(),
}
2. Save the Program from the form
To save the Program created by the user on staff member, simply add form.save() to your link(request) function:
views.py
def link(request):
if request.method == 'POST':
form = ProgramForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse("we maybe getting somewhere")
else:
return HttpResponse("keep working")
Hope this helps!
I was able to do a query from views.py and pass if to the template.
Views.py
def function_page(request, Function_id):
assignments = Function.objects.get(id=Function_id)
programs = assignments.programs.all()
context = {
'assignments': assignments,
'programs' : programs
}
return render (request, 'function.html', context)
HTML
{% for program in programs %}
<option value="{{program.title}}">{{program.title}}</option>
{% endfor %}

WTForms and Django OneToOneFields

I have a Django model which extends the auth User class, but I can't find a way to render data from both models in a single form.
For example, let's extend the User class with a country field using a One2OneField:
from django.contrib.auth.models import User
import pycountry
class Account(models.Model):
user = models.OneToOneField(User, primary_key=True)
COUNTRY_CHOICES = [(country.alpha2, country.name) for country in pycountry.countries]
country = models.CharField(max_length=2, choices=COUNTRY_CHOICES, default='US')
Now let's create a form which contains elements from both models:
class AccountSettingsForm(Form):
first_name = TextField(u'First name:', [validators.Length(min=2, max=35,message=u'First name should be between 2 and 35 characters.')])
last_name = TextField(u'Last name:', [validators.Length(min=2, max=35,message=u'Last name should be between 2 and 35 characters.')])
email = EmailField(u'E-mail address:', [validators.Email(message=u'Invalid e-mail address.')])
COUNTRY_CHOICES = [(country.alpha2, country.name) for country in pycountry.countries]
country = SelectField(u'Country', [valid_country,],choices=COUNTRY_CHOICES)
Now, on my "account settings" page, I have this:
#login_required
def update_settings(request):
form = AccountSettingsForm(request.POST, obj=request.user)
if request.method=='POST' and form.validate():
#processing goes here
context = {'form': form}
return render(request, 'account/settings.html', context)
When opening the page, only the info from "User" (like first name, last name, mail address,...) is displayed. However, the "country" part is not retrieved from the model.
What do I need to do to get both displayed on the same form? I don't see how I can explicitly bind the "country" field of the form to the user.account.country field on the model.
You can override the __init__() method like this to populate the data from obj.account (is that even the default name? I always use the related_name option).
class AccountSettingsForm(Form):
def __init__(self, formdata=None, obj=None, prefix='', data={}, meta=None, **kwargs):
data['country'] = obj.account.country
# etc
super(AccountSettingsForm, self).__init__(formdata, obj, prefix,
data, meta, **kwargs)
Seems it was easier than I thought.
Replace this:
form = AccountSettingsForm(request.POST, obj=request.user)
with:
form = AccountSettingsForm(request.POST, obj=request.user, country=request.user.account.country)

Making initialized foreign key field as read-only in the form

I have a foreign key field in my form, initialized with:
form=myform(intial= {'val':abc.objects.get(pk=id)})
I have tried the following set of codes individually to make the field "val" as a read only value.
form.fields['val'].widget = forms.HiddenInput()
form.fields['val'].widget.attrs['readonly'] = True
form.fields['val'].widget.attrs['disabled'] = True
form.fields['val'].widget.attrs['disabled'] = 'disabled'
form.fields['val'].widget.attrs['disabled'] = False
form.fields['val'].widget.attrs['display_only'] = True
form.fields['val'].widget.attrs['editable'] = False
Only HiddenInput worked but it didn't show the field in the form, while disabled showed an error. readonly worked on all fields except the foreign key field.
I did this by overriding the Select widgets render method, and changing its output.
class ReadOnlySelect(Select):
"""
This should replace the Select widget with a disabled text widget displaying the value,
and hidden field with the actual id
"""
def render(self, name, value, attrs=None, choices=()):
final_attrs = self.build_attrs(attrs, name=name)
display = "None"
for option_value, option_label in chain(self.choices, choices):
if str(option_value) == (value) :
display = option_label
output = format_html('<input type=text value="%s" disabled="disabled" ><input type="hidden" value="%s" %s> ' % (display, value, flatatt(final_attrs)))
return mark_safe(output)
fuller example on Django snippets.
https://djangosnippets.org/snippets/10436/
I have similar problem and I decided to use different approach. Firstly I define my own class for form, for example this way:
class MyForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = YourModel
exclude = ('foreign_key_field')
And then in models myModel did sth like this:
foreign_key_field = models.ForeignKey(KeyField)
def set_values(self, *args, **kwargs):
foreign_key_field = kwargs.pop('foreign_key_field')
I define function to set fields values that are read only. I call this method
after creating form object in views.py. (after submiting form)
And at last views.py:
if form.is_valid():
new_instance = form.save(commit=False)
new_instance.set_values(
foreign_key_field='YOUR VALUE',
)
new_instance.save()
I exclude this fields from form but after creating instance I am setting values. After setting values I save to db.

Categories