My form is called twice but why? - python

When I try to save my form i meet this error :
Exception Value:
Cannot assign None: "TriggerService.user" does not allow null values.
Exception Location: /usr/local/lib/python2.6/dist-packages/django/db/models/fields/related.py in __set__, line 362
here are my models, forms and views
models.py
class TriggerService(models.Model):
"""
TriggerService
"""
provider = models.ForeignKey(TriggerType, related_name='+', blank=True)
consummer = models.ForeignKey(TriggerType, related_name='+', blank=True)
description = models.CharField(max_length=200)
user = models.ForeignKey(User)
date_created = models.DateField(auto_now_add=True)
forms.py
class TriggerServiceForm(forms.ModelForm):
"""
TriggerService Form
"""
class Meta:
"""
meta to add/override anything we need
"""
model = TriggerService
widgets = {
'description':\
TextInput(attrs={'placeholder':\
_('A description for your new service')}),
}
exclude = ('user',
'date_created')
provider = forms.ModelChoiceField(queryset=TriggerType.objects.all())
consummer = forms.ModelChoiceField(queryset=TriggerType.objects.all())
def save(self, user=None):
myobject = super(TriggerServiceForm, self).save(commit=False)
print "i am:"
print user
myobject.user = user
myobject.save()
views.py
class TriggerServiceCreateView(CreateView):
form_class = TriggerServiceForm
template_name = "triggers/add_trigger.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(TriggerServiceCreateView, self).dispatch(*args, **kwargs)
def form_valid(self, form):
self.object = form.save(user=self.request.user)
return super(TriggerServiceCreateView, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(TriggerServiceCreateView, self).get_context_data(**kwargs)
context['action'] = 'add_trigger'
return context
Like you can see in my forms I added a print to follow what happens here
and strangely the save of my form i done twice, why ? and how to avoid this behavior ?
i am:
foxmask
i am:
None

This:
def form_valid(self, form):
# call form.save manually: creates "i am: foxmask"
self.object = form.save(user=self.request.user)
# Call parent form_valid(), CreateView.form_valid() ...
# ... which also calls form.save() without user argument !!
# So, it should create "i am: None" because the default value for
# the user argument of the save() method of your form is None.
return super(TriggerServiceCreateView, self).form_valid(form)
And CreateView.form_valid() calls TriggerServiceForm.save() with user=None.
So you can't use super() here as it will call the direct parent.
Why not keep it simple:
from django import http
# ...
def form_valid(self, form):
self.object = form.save(user=self.request.user)
return http.HttpResponseRedirect(self.get_success_url())

Related

How to use objects specified by a manager instead of the default objects in a class based or function view?

My code:
models.py
class EmployeeManager(models.Manager):
def get_queryset(self):
return super().get_queryset().exclude(employed=False)
class NotEmployedEmployee(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(employed=False)
class Employee(models.Model):
objects = EmployeeManager()
not_employed = NotEmployedEmployees()
name = models.CharField(max_length=250)
employed = models.BooleanField(default=True, blank=True, null=True)
views.py
class EmployeeListView(ListView):
model = Employee
template_name = 'tmng/employee_list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
resultset = EmployeeFilter(self.request.GET, queryset=self.get_queryset())
context['filter'] = resultset
return context
class EmployeeUpdateView(UpdateView):
template_name = 'tmng/employee_update.html'
model = Employee
fields = '__all__'
def form_valid(self, form):
self.name = form.cleaned_data.get('name')
return super().form_valid(form)
def get_success_url(self):
messages.success(self.request, f'Employee "{self.name}" changed!')
return '/'
For all my currently working employees my list and update view works fine.
But I also want a list/update-view for my not-employed employees so I can 'reactivate' them once they rejoin the company.
For the list view I found a semi-solution by using a function based view.
views.py
def not_employed_employee_list_view(request, *args, **kwargs):
template_path = 'tmng/employee_not_employed.html'
context = {'employees': Employee.not_employed.all()}
return render(request, template_path, context)
So what I'm looking for is a way to see list/update non employed employees. Is there a way to say to class based / functions views to use not the default employees but the 'non_employed' employees?
I did not create new templates, but just created a new class based list view
class EmployeeNotEmployedListView(EmployeeListView, ListView):
def get_queryset(self):
return Employee.not_employed.all()
And for the update view, I updated the default Employee update view
class EmployeeUpdateView(UpdateView):
template_name = 'tmng/employee_update.html'
model = Employee
fields = '__all__'
def get_queryset(self):
return Employee.objects.all() | Employee.not_employed.all()
def form_valid(self, form):
self.name = form.cleaned_data.get('name')
return super().form_valid(form)
def get_success_url(self):
messages.success(self.request, f'Employee "{self.name}" changed!')
return '/'

Using Validators in Django not working properly

I have recently learning about Validators and how they work but I am trying to add a function to my blog project to raise an error when a bad word is used.
I have a list of bad words in a txt and added the code to be in the models.py the problem is that nothing is blocked for some reason I am not sure of.
Here is the models.py
class Post(models.Model):
title = models.CharField(max_length=100, unique=True)
---------------other unrelated------------------------
def validate_comment_text(text):
with open("badwords.txt") as f:
censored_word = f.readlines()
words = set(re.sub("[^\w]", " ", text).split())
if any(censored_word in words for censored_word in CENSORED_WORDS):
raise ValidationError(f"{censored_word} is censored!")
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
content = models.TextField(max_length=300, validators=[validate_comment_text])
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now=True)
here is the views.py:
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html" # <app>/<model>_<viewtype>.html
def get_context_data(self, *args, **kwargs):
context = super(PostDetailView, self).get_context_data()
post = get_object_or_404(Post, slug=self.kwargs['slug'])
comments = Comment.objects.filter(
post=post).order_by('-id')
total_likes = post.total_likes()
liked = False
if post.likes.filter(id=self.request.user.id).exists():
liked = True
if self.request.method == 'POST':
comment_form = CommentForm(self.request.POST or None)
if comment_form.is_valid():
content = self.request.POST.get('content')
comment_qs = None
comment = Comment.objects.create(
post=post, user=self.request.user, content=content)
comment.save()
return HttpResponseRedirect("blog/post_detail.html")
else:
comment_form = CommentForm()
context["comments"] = comments
context["comment_form"] = comment_form
context["total_likes"] = total_likes
context["liked"] = liked
return context
def get(self, request, *args, **kwargs):
res = super().get(request, *args, **kwargs)
self.object.incrementViewCount()
if self.request.is_ajax():
context = self.get_context_data(self, *args, **kwargs)
html = render_to_string('blog/comments.html', context, request=self.request)
return JsonResponse({'form': html})
return res
class PostCommentCreateView(LoginRequiredMixin, CreateView):
model = Comment
form_class = CommentForm
def form_valid(self, form):
post = get_object_or_404(Post, slug=self.kwargs['slug'])
form.instance.user = self.request.user
form.instance.post = post
return super().form_valid(form)
Here is my trial which didn't work
def validate_comment_text(sender,text, instance, **kwargs):
instance.full_clean()
with open("badwords.txt") as f:
CENSORED_WORDS = f.readlines()
words = set(re.sub("[^\w]", " ", text).split())
if any(censored_word in words for censored_word in CENSORED_WORDS):
raise ValidationError(f"{censored_word} is censored!")
pre_save.connect(validate_comment_text, dispatch_uid='validate_comment_text')
I am new learner so if you could provide some explanation to the answer I would be grateful so that I can avoid repeating the same mistakes.
I'm sure there are many ways to handle this, but I finally decided to adopt a common practice in all my Django projects:
when a Model requires validation, I override clean() to collect all validation logic in a single place and provide appropriate error messages.
In clean(), you can access all model fields, and do not need to return anything; just raise ValidationErrors as required:
from django.db import models
from django.core.exceptions import ValidationError
class MyModel(models.Model):
def clean(self):
if (...something is wrong in "self.field1" ...) {
raise ValidationError({'field1': "Please check field1"})
}
if (...something is wrong in "self.field2" ...) {
raise ValidationError({'field2': "Please check field2"})
}
if (... something is globally wrong in the model ...) {
raise ValidationError('Error message here')
}
The admin already takes advantages from this, calling clean() from ModelAdmin.save_model(),
and showing any error in the change view; when a field is addressed by the ValidationError,
the corresponding widget will be emphasized in the form.
To run the very same validation when saving a model programmatically, just override save() as follows:
class MyModel(models.Model):
def save(self, *args, **kwargs):
self.full_clean()
...
return super().save(*args, **kwargs)
Proof:
file models.py
from django.db import models
class Model1(models.Model):
def clean(self):
print("Inside Model1.clean()")
def save(self, *args, **kwargs):
print('Enter Model1.save() ...')
super().save(*args, **kwargs)
print('Leave Model1.save() ...')
return
class Model2(models.Model):
def clean(self):
print("Inside Model2.clean()")
def save(self, *args, **kwargs):
print('Enter Model2.save() ...')
self.full_clean()
super().save(*args, **kwargs)
print('Leave Model2.save() ...')
return
file test.py
from django.test import TestCase
from project.models import Model1
from project.models import Model2
class SillyTestCase(TestCase):
def test_save_model1(self):
model1 = Model1()
model1.save()
def test_save_model2(self):
model2 = Model2()
model2.save()
Result:
❯ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
Enter Model1.save() ...
Leave Model1.save() ...
.Enter Model2.save() ...
Inside Model2.clean()
Leave Model2.save() ...
.
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
Destroying test database for alias 'default'...
Validators run only when you use ModelForm. If you directly call comment.save(), validator won't run. link to docs
So either you need to validate the field using ModelForm or you can add a pre_save signal and run the validation there (you'll need to manually call the method, or use full_clean to run the validations).
Something like:
from django.db.models.signals import pre_save
def validate_model(sender, instance, **kwargs):
instance.full_clean()
pre_save.connect(validate_model, dispatch_uid='validate_models')

Inline formset causing error: 'NoneType' object has no attribute 'is_ajax'

I am using bootstrap-modal-forms to show a user a formset with some inline forms. It is possible for the user to save the form if data is only entered into the original form, but if the inline formset has data then I get the following error:
'NoneType' object has no attribute 'is_ajax'
The inline formset was working correctly before I tried to implement them in the modal form. The problem seems to arise only when the inline formset (projectimages) is saved it is a NoneType.
My views.py
class ProjectCreate(BSModalCreateView):
form_class = ProjectForm
template_name = 'project_form.html'
success_message = 'Success: %(project_name)s was created.'
def get_success_url(self):
return reverse_lazy('project-detail', kwargs={'project': self.object.slug})
def get_context_data(self, **kwargs):
data = super(ProjectCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['projectimages'] = ProjectFormSet(self.request.POST, self.request.FILES,)
else:
data['projectimages'] = ProjectFormSet()
return data
def form_valid(self, form):
form.instance.date_created = timezone.now()
context = self.get_context_data()
projectimages = context['projectimages']
with transaction.atomic():
self.object = form.save()
if projectimages.is_valid():
projectimages.instance = self.object
projectimages.save()
return super(ProjectCreate, self).form_valid(form)
My forms.py
class ProjectForm(BSModalForm):
class Meta:
model = Project
exclude = ['date_created', 'slug']
ProjectFormSet = inlineformset_factory(
Project,
ProjectImage,
can_delete=True,
form=ProjectForm,
extra=1,
)
My models.py
class Project(models.Model):
project_name = models.CharField(max_length=100)
date_created = models.DateTimeField('Created on')
slug = models.SlugField(unique=True)
def __str__(self):
return self.project_name
def save(self, *args, **kwargs):
self.slug = slugify(str(self))
super(Project, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('project-list')
class ProjectImage(models.Model):
image = models.ImageField(verbose_name='Additional Images', upload_to=project_directory_path)
project = models.ForeignKey(Project, on_delete=models.PROTECT)
annotation = models.CharField(max_length=200, blank=True)
I expect the user to be able to add as many images to the modal formset as they like.
The BSModalForm expects you to initialise it with the request. This happens in your BSModalCreateView for the main form but not for your formset, because you initialise it manually.
So when initialising, just add the form_kwargs attribute:
if self.request.POST:
data['projectimages'] = ProjectFormSet(
self.request.POST, self.request.FILES,
form_kwargs={'request': self.request})
else:
data['projectimages'] = ProjectFormSet(form_kwargs={'request': self.request})
Note that I think the form you set in ProjectFormSet is wrong, because it should be a form for a ProjectImage model, not a Project. It should actually be called ProjectImageFormSet to better reflect what it is.
You probably want to remove form=ProjectForm as it probably doesn't need to be a BSModalForm (not sure about that). In that case you should not pass the request in form_kwargs. If not, you just need to create another ProjectImageForm class.
Finally, you should not return super().form_valid() because that will save the main form a second time (you already did). Do the redirect yourself.

Django: Use post() in UpdateView to populate form's fields with instance's existing field values

I have trouble getting the current instance's fields on my UpdateView. How do I get the specific instance based on its id?
views.py
class ShowUpdate(UpdateView):
model = Show
fields = ['description', 'season', 'episode']
def post(self, request, **kwargs):
request.POST = request.POST.copy()
request.POST['description'] = "how to get instance description?" # problem here
request.POST['season'] = 2
return super(ShowUpdate, self).post(request, **kwargs)
models.py
class Show(models.Model):
owner = models.ForeignKey(User, null=True, default=True, related_name='o')
title = models.CharField(max_length=100)
description = models.TextField(default='N/A', blank=True, max_length=250)
season = models.IntegerField(default=0)
episode = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse('show:index')
def __str__(self):
return self.title
Look to the UpdateView docs
This View has method get_object(self, queryset=None)
In you case just need to call it in POST method something like this:
class ShowUpdate(UpdateView):
model = Show
fields = ['description', 'season', 'episode']
def post(self, request, **kwargs):
self.object = self.get_object()
request.POST = request.POST.copy()
request.POST['description'] = self.object.description
request.POST['season'] = 2
return super(ShowUpdate, self).post(request, **kwargs)

How to assign the User object to save method in Django

I am trying to log the activities during save operation to track all the changes to user model. my approach is as follows.
class User(AbstractUser):
undergrad_college = models.CharField(max_length=20, choices=COLLEGE_CHOICES)
undergrad_degree = models.CharField(max_length=20, choices=COLLEGE_DEGREES)
postgrad_college = models.CharField(max_length=20, choices=COLLEGE_CHOICES)
postgrad_degree = models.CharField(max_length=20, choices=COLLEGE_DEGREES)
currently_working_on = models.TextField()
previous_work_experience = models.TextField()
previous_internship_experience = models.TextField()
def __str__(self):
return self.username
def save(self, *args, **kwargs):
Log(user=User, actions="Updated profile",
extra={"undergrad_college": self.undergrad_college,
"undergrad_degree": self.undergrad_degree,
"postgrad_college": self.postgrad_college,
"postgrad_degree": self.postgrad_degree,
"currently_working_on": self.currently_working_on,
"previous_work_experience": self.previous_work_experience,
"previous_internship_experience": self.previous_internship_experience
})
super(User, self).save(args, **kwargs)
my views are like this for handling the logging.
class ActivityMixin(LoginRequiredMixin):
def get_context_data(self, **kwargs):
context = super(ActivityMixin, self).get_context_data(**kwargs)
context['activities'] = Log.objects.filter(user=self.request.user)
return context
class IndexListView(ActivityMixin, ListView):
template_name = 'pages/home.html'
model = User
I get this error while performing the update action.
Cannot assign "<class 'users.models.User'>": "Log.user" must be a "User" instance.
Update view is as follows
class UserUpdateView(LoginRequiredMixin, UpdateView):
form_class = UserForm
# we already imported User in the view code above, remember?
model = User
# send the user back to their own page after a successful update
def get_success_url(self):
return reverse("users:detail",
kwargs={"username": self.request.user.username})
def get_object(self, **kwargs):
# Only get the User record for the user making the request
return User.objects.get(username=self.request.user.username)
How to assign the User model instance to the Log function. I cant get this working. I am Django newbie.
Looks like pretty straightforward, replace User with self:
Log(user=User, ...
Log(user=self, ...

Categories

Resources