I want to create a notification to django admin whenever a category is added in database. The admin should then click the boolean field and publish the category.
You could override the save() method on the Categry model - here is some sample basic code...
class Category(models.Model):
def save(self, *args, **kwargs):
if not self.pk:
#no pk so it is new
try:
send_mail('Subject here', 'Here is the message.', 'from#example.com',
['superuser#mail.com'], fail_silently=True)
except:
# do something a bit more elaborate here, like logging
pass
else:
#do something if it is an update or...
pass
super(Category, self).save(*args, **kwargs)
T go this route, just remember to import the send_mail functionality...
from django.core.mail import send_mail
Also, you could get the super users on the fly from the db - or import from the settings file - I have it hardcoded as an example.
EDIT: See Brandon's comment regarding post_save. That is probably a better solution, albeit slightly more advanced. If you don't want to go that route, please wrap the email logic in a try/except block to avoid something secondary like the email failing from blowing up your save.
Related
I've read through several similar questions here, but I still can't seem to find a solution:
I am using the django-registration package and I want to make a registration form that does not have an email field, or at least not required. I have tried many different ways, but here is one:
# in forms.py
from registration.forms import RegistrationForm
class ExpRegistrationForm(RegistrationForm):
# email = forms.EmailField(label="E-mail",required=False)
def __init__(self, *args, **kwargs):
super(RegistrationForm, self).__init__(*args, **kwargs)
self.fields['email'].required = False
Then I link this to a custom view:
# in views.py
from registration.backends.simple.views import RegistrationView as BaseRegistrationView
class ExpRegistrationView(BaseRegistrationView):
form_class = ExpRegistrationForm
Then in urls, after importing views, I add to urlpatterns:
url(r'^../accounts/register/$',views.ExpRegistrationView.as_view(), name='registration_register')
I've shown two attempts above, but I also tried using del or pop to try to get rid of email from my form... And I always get a field error, saying that email is required...
Any suggestions? Or more information needed?
I am using Django DeleteView to delete items in my database. I have use separate template to show the delete confirmation message, but when I press the yes button I get ProtectedError since customer table is linked with Accounts table. Hence I want to handle the ProtectedError and give user another message in the same template.
Here is the code I have used to perform the delete:
class Customer(DeleteView):
#Delete Customers
model = Customer
template_name = 'project_templates/delete_customer.html'
def get_success_url(self):
return reverse('inactive_customers')
It would be really great if someone can suggest me a method to handle this situation.
You should be able to catch the exception. When you look at the DeletionMixin:
https://github.com/django/django/blob/master/django/views/generic/edit.py#L256
You can override the post method and achieve something like:
def post(self, request, *args, **kwargs):
try:
return self.delete(request, *args, **kwargs)
except ProtectedError:
# render the template with your message in the context
# or you can use the messages framework to send the message
Hope this helps.
I had the same issue, and overriding the delete() method worked for me on Django 3.2. I used the messages framework to display the error message - that requires additional setup (see https://docs.djangoproject.com/en/dev/ref/contrib/messages/):
from django.db.models import ProtectedError
from django.contrib import messages
.
.
.
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
try:
self.object.delete()
except ProtectedError:
messages.error(request, "custom error message")
finally:
return redirect(success_url)
How to setup email confirmation in Pinax?, Do I need to setup SMTP server or something like that?, I tried to find documentation about that but failed. Can anyone redirect me to documentation or any related article explaning about this?, Pinax uses emailconfirmation app. I browsed the code of emailconfirmation, But It doesn't include any settings about host or server.
The integration of emailconfirmation is pretty straightforward. You don't have to set up any mailserver, if you have an existing mailserver that you can use to send your mails. You just have to fill in the data used for sending the mails in your standard Django settings, and emailconfirmation is going to use that:
# e-mail settings
# XXXXXXXXXXXXXXXXXXXXXXX THESE ARE NOT YET PRODUCTIONREADY!
EMAIL_HOST='mail.your_mailserver.com'
EMAIL_PORT=1025
EMAIL_HOST_USER='your_username'
EMAIL_HOST_PASSWORD='your_password'
To summarize what to do next: You have to create a form for entering an email-adress (The reason why such a form doesn't come with emailconfiguration is somewhat obscure). This can look something like this:
# email form using emailconfirmation
class AddEmailForm(forms.Form):
def __init__(self, *args, **kwargs):
try:
self.user = kwargs.pop('user')
except KeyError:
pass
super(AddEmailForm, self).__init__(*args, **kwargs)
email = forms.EmailField(label="Email",required=True, widget=forms.TextInput())
def clean_email(self):
try:
EmailAddress.objects.get(user=self.user, email=self.cleaned_data["email"])
except EmailAddress.DoesNotExist:
try:
User.objects.get(email = self.cleaned_data['email'])
except User.DoesNotExist:
return self.cleaned_data["email"]
raise forms.ValidationError(u"email address associated with another account.")
def save(self):
try:
self.user.message_set.create(message="Confirmation email sent to %s" % self.cleaned_data["email"])
except AttributeError:
pass
return EmailAddress.objects.add_email(self.user, self.cleaned_data["email"])
This will allow a user to enter an email address, check if the email-adress allready exists and is in use by another account. After that it will add the emailadress to the unconfirmed emailadresses and send an email with a link to the user. The user then can confirm the emailadress by clicking on the link.
That's all there is to it. Let's hope the Pinax guys will do a quality offensive on their docs soon ;)
I have a booking model that needs to check if the item being booked out is available. I would like to have the logic behind figuring out if the item is available centralised so that no matter where I save the instance this code validates that it can be saved.
At the moment I have this code in a custom save function of my model class:
def save(self):
if self.is_available(): # my custom check availability function
super(MyObj, self).save()
else:
# this is the bit I'm stuck with..
raise forms.ValidationError('Item already booked for those dates')
This works fine - the error is raised if the item is unavailable, and my item is not saved. I can capture the exception from my front end form code, but what about the Django admin site? How can I get my exception to be displayed like any other validation error in the admin site?
In django 1.2, model validation has been added.
You can now add a "clean" method to your models which raise ValidationError exceptions, and it will be called automatically when using the django admin.
The clean() method is called when using the django admin, but NOT called on save().
If you need to use the clean() method outside of the admin, you will need to explicitly call clean() yourself.
http://docs.djangoproject.com/en/dev/ref/models/instances/?from=olddocs#validating-objects
So your clean method could be something like this:
from django.core.exceptions import ValidationError
class MyModel(models.Model):
def is_available(self):
#do check here
return result
def clean(self):
if not self.is_available():
raise ValidationError('Item already booked for those dates')
I haven't made use of it extensively, but seems like much less code than having to create a ModelForm, and then link that form in the admin.py file for use in django admin.
Pretty old post, but I think "use custom cleaning" is still the accepted answer. But it is not satisfactory. You can do as much pre checking as you want you still may get an exception in Model.save(), and you may want to show a message to the user in a fashion consistent with a form validation error.
The solution I found was to override ModelAdmin.changeform_view(). In this case I'm catching an integrity error generated somewhere down in the SQL driver:
from django.contrib import messages
from django.http import HttpResponseRedirect
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
try:
return super(MyModelAdmin, self).changeform_view(request, object_id, form_url, extra_context)
except IntegrityError as e:
self.message_user(request, e, level=messages.ERROR)
return HttpResponseRedirect(form_url)
The best way is put the validation one field is use the ModelForm... [ forms.py]
class FormProduct(forms.ModelForm):
class Meta:
model = Product
def clean_photo(self):
if self.cleaned_data["photo"] is None:
raise forms.ValidationError(u"You need set some imagem.")
And set the FORM that you create in respective model admin [ admin.py ]
class ProductAdmin(admin.ModelAdmin):
form = FormProduct
I've also tried to solve this and there is my solution- in my case i needed to deny any changes in related_objects if the main_object is locked for editing.
1) custom Exception
class Error(Exception):
"""Base class for errors in this module."""
pass
class EditNotAllowedError(Error):
def __init__(self, msg):
Exception.__init__(self, msg)
2) metaclass with custom save method- all my related_data models will be based on this:
class RelatedModel(models.Model):
main_object = models.ForeignKey("Main")
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.main_object.is_editable():
super(RelatedModel, self).save(*args, **kwargs)
else:
raise EditNotAllowedError, "Closed for editing"
3) metaform - all my related_data admin forms will be based on this (it will ensure that admin interface will inform user without admin interface error):
from django.forms import ModelForm, ValidationError
...
class RelatedModelForm(ModelForm):
def clean(self):
cleaned_data = self.cleaned_data
if not cleaned_data.get("main_object")
raise ValidationError("Closed for editing")
super(RelatedModelForm, self).clean() # important- let admin do its work on data!
return cleaned_data
To my mind it is not so much overhead and still pretty straightforward and maintainable.
from django.db import models
from django.core.exceptions import ValidationError
class Post(models.Model):
is_cleaned = False
title = models.CharField(max_length=255)
def clean(self):
self.is_cleaned = True
if something():
raise ValidationError("my error message")
super(Post, self).clean()
def save(self, *args, **kwargs):
if not self.is_cleaned:
self.full_clean()
super(Post, self).save(*args, **kwargs)
I am using django-voting as a voting application for two of my models. Those both models have fields "author".
How can I restrict a user from voting on a model that has this particular user set as it's author without modifying django-voting app?
Django middleware is the first thing that comes to my mind, but I don't understand it's "proces_view" function. If you think middleware is the right way could you please give an example of how to do it.
Add this code anywhere in your settings.py:
from voting.managers import VoteManager
def check_user(func):
def wrapper(self, obj, user, vote):
if obj.user != user:
return func(self, obj, user, vote)
else:
return None
# or raise some exception
return wrapper
VoteManager.record_vote = check_user(VoteManager.record_vote)
I didn't run this code, maybe it's incorrect, but I hope idea is clear
Rather than a middleware hack, why not reroute requests to that particular URI through another view? Then you can performs whatever logic you like, and subsequently call the original view if appropriate.
Another idea is to use the post_save signal
like so:
from django.db.models.signals import post_save
from voting.models import Vote
def check_user(sender, instance, **kwargs):
if instance.user == instance.object.user:
instance.delete()
# do some other stuff to tell the user it didn't work
post_save.connect(check_user, sender=Vote)
The benefit of doing this vs overriding VoteManager.record_vote is that it's less tightly coupled to the voting module, and if they make changes it's less likely to break your code
edit: as in Glader's answer, you need to make sure that all the objects you're voting on have a 'user' attribute.