Django: Update object from view, with no model or template - django

I'm currently working on a Django app that allows users to set synonyms for keywords.
Its using the following model:
class UserSynonym(models.Model):
user = models.ForeignKey(Tenant)
key = models.CharField(max_length=255)
value = models.CharField(max_length=255)
A ListView provides users with an overview, and each entry is followed by an 'edit' button that links to a relevant
EditView that allows the user to change the 'value' parameter.
I now want to add a button that allows the user to quickly reset the object to its original meaning (Set the 'value' to the value of 'key')
I want this to be a single button, without any forms or html templates. Just a button on the list that, when pressed, 'resets' the value of the model.
I reckon I have to edit an EditView for this task, however, these views keep demaning that I supply them with either a form or a HTML template/page
Which is not what I want to do.
Is there a way to change the EditView so that it changes the value of the object, without redirecting the user to a form or a new webpage?
EDIT:
for completeness sake I've added the UpdateView as I'm currently using it
class SynonymUpdate(UserRootedMixin, UpdateView):
model = UserSynonym
form_class = SynonymCreateForm
def get_form_kwargs(self):
kwargs = super(SynonymUpdate, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
form.instance.linked_user = self.kwargs.get('user')
return super(SynonymUpdate, self).form_valid(form)
def get_success_url(self, **kwargs):
return reverse('synonym_list', args=[self.request.user.id])

I sort of solved my problem. I gave up on using a class based view and used the following function instead:
def SynonymReset(request, user_id, pk):
"""(re)sets the Synonym.value to the value of Synonym.key"""
#Get relevant variables
currentuser = User.objects.get(id=request.user.id)
currentsynonym = Synonym.objects.get(id = pk)
#(re)set object & save
currentsynonym.value = currentsynonym.key
currentsynonym.save()
#Return to the listview.
return redirect('synonym_list', user=current_user)
This way the value is reset, without going to a seperate webpage. I still hope to one day find out how to do this in a class based view. But for now this will suffice.

Related

Django: Display user's previous choices for a ModelForm in the template

I am trying to create a user profile page where users can see and update their preferences for certain things, like whether they are vegetarian, or have a particular allergy, etc. I want the data to be displayed as a form, with their current preferences already populating the form fields.
So I've created the following Model:
class FoodPreferences(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) # One user has one set of food prefs
vegetarian = models.BooleanField()
vegan = models.BooleanField()
...
that's referenced in my forms.py:
class FoodPreferencesForm(forms.ModelForm):
class Meta:
model = FoodPreferences
exclude = ('user', )
I've tried creating a view that inherits FormView and then referencing the form, like this:
class UserProfileView(generic.FormView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
This saves the form to a instance of the model correctly, but obviously it just displays the blank form again, after updating, so the user has no idea what their current preferences are.
To implement this I thought I might need to override get() and post() to get the instance of FoodPreferences for the user, and then pass those values into the form like you would a request.POST object. However, firstly, I don't know how to do that, and secondly I'd be taking responsibility for correctly updating the database, which the FormView was already doing.
This is what I've got for that solution:
def get(self, request, *args, **kwargs):
prefs = FoodPreferences.objects.get(user=request.user)
form = self.form_class(prefs)
return render(request, self.template_name, {'form': form, })
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if not form.is_valid():
return render(request, self.template_name, {'form': form, 'error': 'Something went wrong.'})
curr_prefs = FoodPreferences.objects.update_or_create(form.fields)
prefs.save()
return render(request, self.template_name, {'form': form, })
but I get a TypeError: argument of type 'FoodPreferences' is not iterable on the line in get():
form = self.form_class(prefs)
because it's not expecting a model instance.
Am I thinking about this in the right way? This seems like a common enough problem that Django would have something inbuilt to do it, but I can't find anything.
You should only rarely need to define get or post in a class-based view, and you definitely don't here.
To start with, you need to use a more appropriate base class for your view. Here you want to update an existing item, so you should use UpdateView.
Secondly, you need to tell the class how to get the existing object to update, which you can do by definining get_object. So:
class UserProfileView(generic.UpdateView):
template_name = "registration/profile.html"
form_class = FoodPreferencesForm
success_url = reverse_lazy('user_profile')
def get_object(self, queryset=None):
return self.request.user.foodpreferences
# or, if you aren't certain that the object already exists:
obj, _ = FoodPreferences.objects.get_or_create(user=self.request.user)
return obj

Class Based FormView

I have researched this issue for a couple of days and can't seem to find what I'm looking for exactly. I have searched ModelChoiceField as well as ChoiceField on StackOverflow as well as Google and there are many variations of my question but nothing exactly. In a nutshell, I am trying to use a Class Based FormView and then capture the user selection and pass it to a Class Based ListView. Here is my code.
Forms.Py
class BookByStatus(forms.Form):
dropdown = forms.ChoiceField(choices=[],required=False)
def __init__(self, user, *args, **kwargs):
super(BookByStatus, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].choices =
Book.objects.values_list("author","author").distinct("Publisher")
The code above works fine, and shows me the output I'm looking for on my view. No issues there....Then I have my FormView...
class BookByStatusView(LoginRequiredMixin,FormView):
model = Book
form_class = BookByStatus
template_name = 'xyz123/publisher.html'
success_url = reverse_lazy('Book:book_by_list',kwargs=
{'dropdown':'dropdown'})
def get_form_kwargs(self):
kwargs = super(BookByStatusView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
self.request.POST['dropdown']
BookByStatusView = form.cleaned_data['dropdown']
return super(BookByStatusView, self).form_valid(form)
The code above works fine, but takes me to the ListView below which I can't seem to pass the dropdown value to....I've tried several different iterations of get_form_kwargs as well as changed my form to ModelChoiceField, but still can't seem to understand how to get a queryset based on the input from the user...
And finally the ListView...
class BookByStatusListView(LoginRequiredMixin,ListView):
model = Book
form_class = BookByStatus
context_object_name = 'book_list'
template_name = 'xyz123/book_by_status_list.html'
paginate_by = 15
def get_queryset(self, *kwargs):
form = self.form_class(self.request.GET)
dropdown = self.kwargs.get('dropdown', None)
if form.is_valid():
return Book.objects.filter(dropdown__icontains=form.
cleaned_data['dropdown'])
return Book.objects.all()
I'm trying to take the dropdown input from the FormView and then pass it to a list view using two separate views. I need to pass the value from the FormView to the ListView. I'm clear on how to get the data in the FormView in the ChoiceField, and how to display a ListView, but I can't seem to figure out how to pass the dropdown data from the FormView to the ListView. I can get the ListView to work, but only with the full queryset, not with a filtered one.
Here's the book model....
class Book(models.Model):
Author CHOICES = (
("New","New"),
("Old","Old"),
)
Author = models.CharField(choices=Author_CHOICES,max_length=10)
Here's the URL...
url(r'^book_by_list/(?
P<dropdown>\w+)/$',views.BookByStatusListView.as_view(),
name='book_by_list'),
Thanks in advance for any suggestions!
Updated Approach...Using request.session. My prior approach would not let me pass the value from the one view to the other, no matter how many get_context_data or get_form_kwargs combinations I tried. Based on the input I received, I began exploring the request.session approach and I've gotten much further. One last piece remains, getting the request.session value in my LISTVIEW so I can filter my querysets accordingly.
class BookByStatusView(LoginRequiredMixin,FormView):
model = Book
form_class = BookByStatus
template_name = 'xyz123/publisher.html'
success_url = reverse_lazy('Book:book_by_list')
def form_valid(self, form):
self.request.session['dropdown'] = form.cleaned_data['dropdown']
return super(BookByStatusView, self).form_valid(form)
def get_form_kwargs(self):
kwargs = super(BookByStatusView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
And in my html template, I leverage the request.session value as...
{{ request.session.dropdown }}
And I reverted the URL back to..
url(r'^book_by_list/$,views.BookByStatusListView.as_view(),
name='book_by_list'),
The last remaining piece is to figure out how to leverage the LISTVIEW with this approach.
My current Listview:
class BookByStatusListView(LoginRequiredMixin,ListView):
model = Book
form_class = BookByStatus
context_object_name = 'book_list'
template_name = 'xyz123/book_by_status_list.html'
paginate_by = 15
def get_queryset(self):
queryset = Book.objects.none()
dropdown = self.request.session.get('dropdown')
if dropdown == 'New':
queryset = Book.objects.all()
elif dropdown == 'Old':
queryset = Book.objects.none()
return queryset
I can't seem to figure out how to pass the dropdown value correctly to the ListView so the queryset is displayed properly. Based on my testing, I don't appear to be capturing dropdown properly in the get_queryset function. Any ideas?
I figured it out. I updated the get_queryset with the proper syntax. Thanks for all of the help to nudge me in the right direction. Last questions..is this the best way to pass a value from one view to another? Is there a better way to do this? Are there any concerns with this approach?
This doesn't work, because redirect creates a new request/response and data from previous are lost. If I understand what you want correctly, one of the options would be to save the drop-down value to session in BookByStatusView and then retrieve it in BookByStatusListView.
You save to session with:
request.session['dropdown_value'] = form.cleaned_data['dropdown']
and retrieve with:
dropdown_value = request.GET.get('dropdown_value')
Here is How to use session part of Django documentation.
EDIT: You can also pass the value as an url parameter like this:
author = 'michael cricthon'
title = 'kongo'
year = [1999, 2000, 2001]
type = ['electronic', 'print', 'hardcover', 'softcover']
params = '?author={}&title={}&&year={}&type={}'.format(
urllib.parse.quote_plus(author),
urllib.parse.quote_plus(title),
','.join(year),
','.join(type))
return HttpResponseRedirect(reverse('search') + params)
The link would look like this:
../search/?author=michael+crichton&title=kongo&year=1999,2000,2001&type=electronic,print,hardcover,softcover
You get parameters with
author = request.GET.get('author')
title = request.GET.get('title')
... etc.

Custom UpdateView in Python

I am trying to get a custom UpdateView to work in Python/Django. I believe that the code that I've writtten is mostly correct, as it seems to be returning the proper Primary Key ID in the URL when I click on the associated dropdown. The problem is that I am not seeing any of the data associated with this record on the screen in update mode. The screen appears in edit mode, but there is no data. I suspect the problem is perhaps the django template in the html form? However, I have played with the form and used {{ form }} and it too returns a blank form. I've played with this all afternoon and I'm out of guesses. Here is my view:
def updating_document(request, pk):
doc = get_object_or_404(Doc, pk=pk)
form = Update_Doc_Form(request.user, request.POST)
if request.method == 'GET':
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('App:main_procedure_menu'))
else:
print("Form is invalid!")
return render(request,'Doc/update_doc.html',{'form':form })
I also have an associated form...
Form.py
class Update_Doc_Form(forms.ModelForm):
class Meta:
model = Doc
exclude = ['user']
doc_name = forms.CharField(widget=forms.TextInput)
description = forms.CharField(required=True,widget=forms.Textarea)
team = forms.CharField(widget=forms.Select)
document = forms.CharField(required=True,widget=forms.Textarea)
def __init__(self, *args, **kwargs):
super(Update_Doc_Form, self).__init__(*args, **kwargs)
self.fields['doc_name'].widget.attrs['class'] = 'name'
self.fields['description'].widget.attrs['class'] = 'description'
self.fields['team'].widget.attrs['class'] = 'choices'
self.fields['team'].empty_label = ''
I'm a newbie, but I do want to use a custom UpdateView so that I can alter some of the fields and pass user information. I feel like the code is close, just need to figure out why it's not actually populating the form with data. Thanks in advance for your help!
What a difference a day makes. Found an answer on SO this morning. Not sure how to credit the person or issue number....
The answer was to add the following line of code to my form:
user = kwargs.pop('object_user')
I also needed to add the following function to my View:
def get_form_kwargs(self):
kwargs = super(ViewName,self).get_form_kwargs()
kwargs.update({'object_user':self.request.user})
return kwargs
This question was answered originally in 2013 by Ivan ViraByan. Thanks Ivan!
I ultimately went with a standard class based UpdateView and scrapped my plans for the custom UpdateView once I was able to figure out how to use the Class Based View(UpdateView) and "pop" off the user information when passing it to the form based on Ivan ViraByan's answer in 2013.
The code above allows you to get the user but not pass it to the ModelForm so that you don't get the unexpected user error.

Django update a model form

I have a modelform:
class UserPreferencesForm(ModelForm):
"""
Form for storing user preferences.
"""
class Meta:
model = UserPreferences
exclude = ('user')
the model:
class UserPreferences(models.Model):
"""
Model for user project preferences.
"""
user = models.OneToOneField(User)
...
and in the views:
...
form = UserPreferencesForm(request.POST or None)
if form.is_valid():
# save the form
prefs = form.save(commit=False)
prefs.user = request.user
prefs.update()
messages.add_message(
request, messages.INFO, 'Your preferences have been updated.'
)
...
I want to ensure that each user only has one set of preferences, so I would like to refactor the view code to use something along the lines of the update() model method instead of checking for object existence and then saving, which would incur more queries.
What is the most efficient way of 'create-or-updating' the model?
Any help much appreciated.
Are you interested in saving the query to detect if a row exists?
In the case, you could do as you describe.. do an update and check if 0 rows were updated, which implies the profile doesn't exist.
updated = Preferences.objects.filter(user=request.user).update(**form.cleaned_data)
if updated == 0:
# create preference object
But an even simpler design pattern is to ensure there is always a preferences table for every user via a signal listening on models.signals.post_save sent by the User class.
Then, you can assume it always exists.
def create_prefs(sender, instance, created, **kwargs):
if created:
# create prefs
models.signals.post_save.connect(create_prefs, sender=User)

Newbie Django Question : Can't find data I thought I preset in a Form

I'm still getting to grips with Django and, in particular, Forms.
I created MyForm which subclasses forms.Form in which I define a field like this :
owner = forms.CharField(widget=forms.HiddenInput)
When I create a new, blank instance of the Form I want to prefill this with the creator's profile, which I'm doing like this :
form = MyForm( {'owner' : request.user.get_profile()} )
Which I imagine sets the owner field of the form to the request.user's id. (The type of the corresponding "owner" field in the models.Model class is ForeignKey of Profile.)
Before rendering the form, I need to check one piece of information about the owner. So I try to access form.owner, but there seems to be no "owner" attribute of the form object. I also tried form.instance.owner, but similarly, no luck.
What am I doing wrong? What have I misunderstood?
You can access this value via the form's data dictionary:
form.data.get('owner')
Initial data in a form should be passed in with the initial kwarg.
Once you've turned the form into a bound form (usually by passing request.POST in as the first argument to instantiate the form, the place you are currently incorrectly providing the initial dictionary), and performed validation with form.is_valid(), the data the user submitted will be in form.cleaned_data, a dictionary. If they changed the initial value, their changed value will be in that dictionary. If not, your initial value will be.
If you don't want to let the user modify the value, then don't have it be a field, instead pass it in as a kwarg, and store it as an instance attribute in form.__init__():
class MyForm(Form):
def __init__(self, *args, profile, **kwargs):
super().__init__(*args, **kwargs)
self.profile = profile
...
form = MyForm(
request.POST if request.POST else None,
profile=request.user.get_profile(),
)
if request.method == "POST" and form.is_valid():
do_stuff_with(form.profile)
Also as with most things, this all gets easier if you drink the Django kool-aid and use generic views.
class MyView(FormView):
form_class = MyForm
...
def get_form_kwargs(self):
return {
**super().get_form_kwargs(),
"profile": self.request.user.get_profile()
}
def form_valid(self, form):
do_stuff_with(form.profile)
return super().form_valid(form)
Or for the initial case whereby you want it to be editable:
class MyView(FormView):
form_class = MyForm
...
def get_initial(self):
return {
**super().get_initial(),
"profile": self.request.user.get_profile()
}
def form_valid(self, form):
do_stuff_with(form.cleaned_data.get("profile"))
return super().form_valid(form)
If MyForm happens to be a form about one single instance of a specific model then this gets even easier with UpdateView instead of FormView. The more you buy into the way Django wants to do things, the less you have to work.