Best practice to pass attribute from ModelForm.clean() to Model.save() - django

class MyModel(models.Model):
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if getattr(self, 'my_attr', False):
# do things
class MyForm(forms.ModelForm):
def clean(self)
cleaned_data = super().clean()
if self.has_changed():
self.instance.my_attr = self.get_the_needed_info()
return cleaned_data
class Meta:
model = MyModel
fields ='__all__'
#admin.register(MyModel)
class MyAdmin(admin.ModelAdmin)
form = MyForm
During MyModel.save(), I need to check for a condition that is evaluated in ModelForm.clean().
During clean(), I assign the attribute my_attr to self.instance.
It is working
it seems to be
thread-safe
(within an atomic transaction).
Is there any reason I miss, that urges a refactoring?

According to django docs, using the ModelForm to set an existing instance attribute, is recommended.
views.py:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author
class AuthorCreate(LoginRequiredMixin, CreateView):
model = Author
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)

Related

__init__() missing 1 required keyword-only argument: 'creator'

I am getting type error while setting current user to created_by field in my model
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = Model
fields = ('name',)
def __init__(self, *args, creator, **kwargs):
super().__init__(*args, **kwargs)
self.creator = creator
def save(self, *args, **kwargs):
self.instance.created_by = self.creator
return super().save(*args, **kwargs)
views.py
class CreatEEView(LoginRequiredMixin, CreateView,):
form_class = ''
template_name = ''
success_url = ''
Models.py
class MYmodel(models.Model):
name = models.CharField()
created_by = models.ForeignKey()
You do not need to use a custom form for that because CreateView is creating ModelForm for you, what you can do is like that:
from django.db import models
class MYmodel(models.Model):
name = models.CharField()
created_by = models.ForeignKey()
Then you can override the form_valid method. This method is called when valid form data has been POSTed.
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import MYmodel
class CreatEEView(LoginRequiredMixin, CreateView):
model = MYmodel
fields = ['name']
def form_valid(self, form):
form.instance.created_by = self.request.user
return super().form_valid(form)
You can find the same example in Django documents in this Link

Django FormView: Add if record does not exist and update if it does exist

I created a FormView and it works fine if the user executed the process the first time. However when it is executed the second time I get an error that the record already exist. This is expected as the user in the model is unique. How can I overcome this problem so that the current record is overwritten by the form.save if the record already exist.
models.py
class ttemp_selection(models.Model):
select_account = models.ForeignKey(tledger_account, on_delete=models.CASCADE)
date_from = models.DateField(default=datetime.today)
date_to = models.DateField(default=datetime.today)
user = models.ForeignKey(custom_user, on_delete=models.CASCADE, unique=True)
def __str__(self):
return self.select_account
forms.py
class Meta:
model = ttemp_selection
fields = ['select_account', 'date_from', 'date_to', 'user']
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(SelectAccountForm, self).__init__(*args, **kwargs)
user = self.request.user
current_company = user.current_company
self.fields['select_account'].queryset = tledger_account.objects.filter(
company=current_company, gl_category='Cash and Bank')
view.py
class sasView(FormView):
template_name = 'cashflow/select_account.html'
form_class = SelectAccountForm
success_url = 'home'
def form_valid(self, form):
form.save()
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(sasView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
I can determine the record by using ttemp_selection.objects.get(user=request.user)
I know I can make use of the UpdateView class but that will create a problem when the record does not exist. It will also add an extra step that is unnecessary.
Assistance will be appreciated.
You can work with a CreateView, and slightly alter the behavior to specify a self.object if that exists:
from django.contrib.auth.mixins import LoginRequiredMixin
class sasView(LoginRequiredMixin, CreateView):
template_name = 'cashflow/select_account.html'
form_class = SelectAccountForm
success_url = 'home'
def get_form(self, *args, **kwargs):
self.object = ttemp_selection.objects.filter(
user=self.request.user
).first()
return super().get_form(*args, **kwargs)
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super(sasView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
It however makes no sense to include the user as field, since - if I understand it correctly - you use the logged in user. By including it, you make it possible that a person forges a POST request, and thus changes the account of a different user. You should omit this filed:
class SelectAccountForm(forms.ModelForm):
class Meta:
model = ttemp_selection
# no user ↓
fields = ['select_account', 'date_from', 'date_to']
# …
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].

Django - How do I populate the user field of a model by default in the admin panel based on the current user logged in?

I am trying to create a question-answer forum in django where only the admins are able to respond to a question asked by all the registered users.
models.py
from django.db import models
from django.contrib.auth.models import User
from datetime import datetime
# Create your models here.
class Question(models.Model):
username=models.ForeignKey(User, on_delete=models.DO_NOTHING)
question=models.CharField(max_length=100)
date=models.DateTimeField(default=datetime.now, blank=True)
def __str__(self):
return self.question
class Comments(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{}-{}'.format(self.question.question, str(self.user.username))
admin.py
from django.contrib import admin
from . models import Question, Comments
# Register your models here.
admin.site.register(Question)
admin.site.register(Comments)
views.py
from django.shortcuts import render, redirect
from . models import Question, Comments
from .forms import CommentForm
# Create your views here.
def addQuestion(request):
if request.method == 'POST':
username = request.user
question = request.POST['question']
question = Question(username=username, question=question)
question.save()
# note=Note(title=title, description=description, username=username)
# note.save()
return redirect('/dashboard')
else:
return render(request, "dashboard/question.html")
def viewQuestion(request, question_id):
viewquestion=Question.objects.get(id=question_id)
comments = Comments.objects.filter(question=viewquestion).order_by('-question_id')
context = {
'viewquestion':viewquestion,
'comments':comments
}
return render (request, 'dashboard/questionview.html', context)
As of now, the admin panel provides a drop down based on which I can select a user, but I need the model to display the authenticated admin user by default in the model before adding a comment rather than an admin manually choosing the username.
This is how it looks like currently.
How do I make the dropdown select the current logged in user by default?
Step 1:-
# Pass request params to your model form
admin.py
class CommentsAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
ModelForm = super(CommentsAdmin, self).get_form(request, obj, **kwargs)
class ModelFormMetaClass(ModelForm):
def __new__(cls, *args, **kwargs):
kwargs['request'] = request
return ModelForm(*args, **kwargs)
return ModelFormMetaClass
fields = (('question'), ('user'), ('content',),)
form = CommentsForm
admin.site.register(Comments, CommentsAdmin)
Step 2:-
# Create your form which you have specified for your admin class of comments model (CommentsAdmin)
form.py
class CommentsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(CommentsForm, self).__init__(*args, **kwargs)
self.fields['user'].initial = self.request.user
class Meta:
model = Comments
exclude = ()

Authentication for class based views in Django

class AdminView(generic.ListView):
model = get_user_model()
fields = ['first_name', 'username', 'is_active']
template_name = 'users/admin.html'
class AdminUpdateView(UpdateView):
model = get_user_model()
fields = ['is_active']
template_name = 'users/user_update.html'
success_url = reverse_lazy('users:admin')
There are two views in django which I have created and I want them to be accessed only when the admin/staff logins. How do I go about it?
You can use the UserPassesTestMixin [Django-doc] and LoginRequiredMixin [Django-doc] mixins, and specify as condition that the user should be an is_superuser. Since you need these twice, we can make first a composite mixin:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class AdminStaffRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
def test_func(self):
return self.request.user.is_superuser or self.request.user.is_staff
Next you can add the mixin to your class-based views:
class AdminView(AdminStaffRequiredMixin, generic.ListView):
model = get_user_model()
fields = ['first_name', 'username', 'is_active']
template_name = 'users/admin.html'
class AdminUpdateView(AdminStaffRequiredMixin, UpdateView):
model = get_user_model()
fields = ['is_active']
template_name = 'users/user_update.html'
success_url = reverse_lazy('users:admin')
You can use UserPassesTestMixin:
from django.contrib.auth.mixins import UserPassesTestMixin
class AdminView(UserPassesTestMixin, generic.ListView):
model = get_user_model()
fields = ['first_name', 'username', 'is_active']
template_name = 'users/admin.html'
def test_func(self):
return self.request.user.is_staff or self.request.user.is_superuser
Use decorators, with #login_required you can tell this views will be only accesseed when user os logged in, you can pass parameters to it too or create one your own to validate if the logged user on the request can see or no your view
With Login Required
from django.contrib.auth.decorators import login_required
#login_required(login_url='/accounts/login/')
class AdminView(generic.ListView):
...
#login_required(login_url='/accounts/login/')
class AdminUpdateView(UpdateView):
...
https://docs.djangoproject.com/en/2.0/topics/auth/default/#the-login-required-decorator
With Permission
from django.contrib.auth.decorators import permission_required
#permission_required('user.is_staff')
def my_view(request):
...
https://docs.djangoproject.com/en/2.0/topics/auth/default/#the-permission-required-decorator
If you want to use the LoginRequiredMixin, you still can. And it is much simpler. Just extend the LoginRequiredMixin in all you classes so that they are like this.
class AdminView(LoginRequiredMixin, generic.ListView):
model = get_user_model()
fields = ['first_name', 'username', 'is_active']
template_name = 'users/admin.html'
class AdminUpdateView(LoginRequiredMixin, UpdateView):
model = get_user_model()
fields = ['is_active']
template_name = 'users/user_update.html'
success_url = reverse_lazy('users:admin')
This ensures that the user is already logged in before allowing any operations. Then, check if the user is an admin by adding the following code to each of the classes;
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_staff:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
Your code should now look like this:
class AdminView(LoginRequiredMixin, generic.ListView):
model = get_user_model()
fields = ['first_name', 'username', 'is_active']
template_name = 'users/admin.html'
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_staff:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class AdminUpdateView(LoginRequiredMixin, UpdateView):
model = get_user_model()
fields = ['is_active']
template_name = 'users/user_update.html'
success_url = reverse_lazy('users:admin')
def dispatch(self, request, *args, **kwargs):
if not self.request.user.is_staff:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
You can use IsAdminUser permission by rest framework
from rest_framework import permissions
class AdminView(generic.ListView):
permission_classes = (permissions.IsAdminUser, )
...

Django - unique_together option with override save method in form

I want to use the unique_together option when user save data within a form and not with the django admin.
models.py:
from django.db import models
from django.contrib.auth.models import User
class ezApp(models.Model):
name = models.SlugField(max_length=50, unique=True )
date_created = models.DateTimeField('date created', auto_now_add=True)
date_updated = models.DateTimeField('date updated', auto_now=True)
created_by = models.ForeignKey(User)
in_use = models.BooleanField()
class Meta:
unique_together = (('name', 'created_by'),)
forms.py
from django.forms import ModelForm
from django.forms.models import BaseModelFormSet
from ezApp.models import *
class BaseEzAppFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(BaseEzAppFormSet, self).__init__(*args, **kwargs)
def save_new(self, form, commit=True):
obj = form.save(commit=False)
obj.created_by = self.user
if commit:
obj.save()
return obj
views.py:
from django.shortcuts import *
from ezApp.models import *
from django.forms.models import modelformset_factory
from django.http import HttpResponseServerError
from ezApp.forms import *
def createEzAppInstance(request):
if request.method == 'POST':
ezAppFormSet = modelformset_factory(ezApp, extra=1, fields=('name'), formset=BaseEzAppFormSet)
formset = ezAppFormSet(request.POST, request.FILES, user=request.user)
if formset.is_valid():
formset.save()
return render_to_response("ezApp/manage_new_ezApp.html", {'formset': formset, 'title': "New App"}, context_instance=RequestContext(request))
else:
error_msg = u"You are not logged in"
return HttpResponseServerError(error_msg)
With unique_together in the Meta of the model, the validation is working only inside django admin but not when I use the form to save new data.
As msc points out you need to override the save method rather than writing your own.
def save(self, *args, **kwargs):
obj = super(BaseEzAppFormSet, self).save(form, *args, commit=False, **kwargs)
obj.created_by = self.user
obj.save()
return obj
It doesn't look like your save_new() method was ever being called in your view.