This is a bit hard to explain but I'll try my best.
I am developing a website where I want a feature where the admins can add an email notification connected to a certain model. The admin should be able to
Choose a model on the website,
What event it should trigger on (create, update or delete),
The email body and text.
For example, if the admin adds an email to a model foo on a create event, the specified email should be sent to some recipient whenever a new foo is added to the database.
I am attempting to implement a new model that contains some reference to the models, using post_save() to send emails, but it's turning out more complex than I thought. I have searched far and wide to find some addon that does this, but I haven't found any.
Does anyone have any tips on specific functionalities that can help with this, or if there's a good addon that I can start with?
Create a new model (called "SendEmail" for example) that contain the relations and the data of the emails is a good idea. May be you can override save and delete methods on the other models. In your views, before save or delete an object, check in your new model "SendEmail" if it is necessary to send and email and pass this object to the save/delete method.
In your views, after check, if you have to send the email:
foo.save(sendemail=sendemail) #where sendemail is a SendEmail object.
if not:
foo.save()
In your existing models:
Class Foo(models.Model):
----
----
def save(self, *args, **kwargs, sendemail=None):
if sendemail:
**the code to send the email**
super().save(*args, **kwargs) # Call the "real" save() method.
EDIT:
To not override all de save method for all your models you can create a new model class with the save method and change the class of your actual models:
Class SendEmailModel(models.Model):
def save(self, *args, **kwargs, sendemail=None):
if sendemail:
**the code to send the email**
super().save(*args, **kwargs) # Call the "real" save() method.
Class Foo(SendEmailModel):
----
----
Related
I am using Django's forms to validate API PATCH requests. In the "view" (which I use in quotes because it isn't really a view directly, it is a restless Resource, but that should be irrelevant here) which handles this patch request, self.data contains a dictionary of changes to some of the fields of the License object. I want to instantiate a ModelForm with the instance of the object to be changed. Clearly, though, I am misunderstanding how this works. See below:
def handle_patch(self, pk):
license = License.objects.get(id=pk)
form = LicenseResourceForm(self.data, instance=license)
if not form.is_valid():
print(form.errors)
If I pass a few fields as data to the above function, form.errors complains about every other required field of the License model, meaning I'm clearly not understanding how setting an instance on a ModelForm works.
I added a few debug prints to Django's ModelForm code itself in the clean() method, and as it begins to do the cleaning process, I can see that self.instance is populated with the instance of License that I expect, which confuses me - the ModelForm object knows the instance, but isn't using it to "fill in the blanks" so to speak.
So what am I misunderstanding? I must be doing this wrong.
EDIT I realize that some of you may want to see the LicenseResourceForm itself, so here it is, including my debug print:
class LicenseResourceForm(ModelForm):
"""Form for License Resource create and change endpoints."""
class Meta(object):
model = License
fields = ['customer', 'service', 'enabled', 'not_valid_before', 'not_valid_after']
def clean(self):
try:
print(self.instance)
super().clean()
except Exception as e:
print(e)
Django forms aren't meant for API use and don't understand PATCH semantics. They are meant for the workflow of a user entering or changing data in a web form, which will always post all the data to the backend. Therefore, all fields listed in the fields attribute of the form will be checked against the data, and any missing fields will be validated as blank.
You could probably fix this by doing something clever to dynamically set the list of fields based on the data supplied, but really you should use the appropriate tool for validating your data. I don't know restless, but django-rest-framework has serializers which can be used for this.
I've been writing a webapp with Django to replace a clumsy, spreadsheet based sports picking game that I play with some friends. I've learned a lot, and had a great time getting to know Django and how to build something like this from scratch.
I recently realized that I wanted to use something more powerful on the frontend (Ember, Angular, etc) with the end goal being a single page app. To that end, I installed Django REST Framework (DRF) and started reading the docs and following the tutorial. It's super interesting, and I'm finally starting to see why a client-server model with an API is really the only way to achieve the smooth interactivity that's all over now.
I'm trying to implement one of my class based views as an API endpoint, and I've been having a lot of trouble conceptualizing it. I thought I'd start with a simple, GET-only endpoint- here's the simple CBV I'm trying to replicate in API form:
class MatchupDetail(DetailView):
template_name = 'app/matchups.html'
context_object_name = 'pick_sheet'
def get_object(self):
#logic to find and return object
def get_opponent(self,username,schedule,week, **kwargs):
#logic to find and return the opponent in the matchup
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
#logic to pull the opponents details and set them in the context
I feel like I have a handle on this flow- a user clicks a link, and this view retrieves the object at the heart of the requested page, supplements it with content in the context, then renders it.
As I began thinking about turning this into an API endpoint, it didn't make a whole lot of sense. Should I be putting all the user-related data into a single JSON response? Or should the frontend basically handle the flow of this logic and the API simply be composed of a collection of endpoints- for example, one to retrieve the object, and one or more to retrieve what's now being passed in the context?
What prompted me to make this post was some trouble with my (super basic) API implementation of the above view:
class MatchupDetailApi(generics.ListAPIView):
queryset = Sheet.objects.all()
serializer_class = SheetSerializer
With serializer:
class SheetSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField()
class Meta:
model = Sheet
I added the user field when I noticed that without it, the returned serialized Sheet objects are literally just the row in the database- an integer ID, integer foreign key to the User object, and so on. With a 'traditional' CBV, the entire objects are returned to the template- so it's very intuitive to access related fields, and with Django it's also easy to traverse object relationships.
Does a REST implementation offer the same sort of thing? From what I've read, it seems like I'll need an extension to DRF (django-rest-multiple-models) to return more than one model in a single response, which leads me to think I should be creating endpoints for every model, and leaving presentation logic to when I take care of the frontend. Is that typical? Or is it feasible to have an API endpoint that does return something like an object and several related objects?
Note: the basic endpoint above stopped working when I added the user to the SheetSerializer. I realized I should have a UserSerializer as well, which is:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
However, when I try to browse the API, i get a TypeError that the first user isn't serializable. Specifically: <User: dkhaupt> is not JSON serializable. Isn't this what the UserSerializer is for?
Is it feasible to have an API endpoint that does return something like
an object and several related objects?
Yes!
And it sounds like you are off to a great start. I would structure it something like this:
class UserSerializer(serializers.ModelSerializer):
"""serializes a user"""
class Meta:
model = User
fields = ('id', 'first_name', 'last_name',)
class SheetSerializer(serializers.ModelSerializer):
"""serializes a sheet, and nests user relationship"""
user = UserSerializer(read_only=True)
class Meta:
model = Sheet
fields = ('id', 'sheet_name', 'user',)
I don't think you need django-rest-multiple-models for what you are trying to achieve. In my sketch (where I'm guessing fieldnames) you will serialize the sheet, and also the associated user object.
You can add fields from another related model using the source attribute.
for example:
class SheetSerializer(serializers.ModelSerializer):
user_id = serializers.ReadOnlyField(source='user.user_id')
username = serializers.ReadOnlyField(source='user.username')
class Meta:
model = Sheet
Here the serializer will return the information from the user model that is related to the Sheet model.
Like if I have a Message model like
class Message(models.Model):
from = models.ForeignKey(User)
to = models.ManyToManyField(User)
def get_authenticated_user_inbox_messages(self):
return Message.objects.filter(to=authenticated_user).all()
authenticated_user being a way to get the currently authenticated user.
In the view, I can get the authenticated user with request.user
But, if I don't want or can't by design to pass this as a parameter, is there a way to get it directly from inside the model?
Any ideas? Thank you very much!
No, this is not possible. A Model object exists independently from a web request. For example, you can define a management command that uses model objects from the command-line, where there is no request.
What I've seen as a good practice is to define a static method on your Model object that takes a User object as input, like this:
class Message(models.Model):
from = models.ForeignKey(User)
to = models.ManyToManyField(User)
#staticmethod
def get_messages_to(user):
return Message.objects.filter(to=user)
#staticmethod
def get_messages_from(user):
return Message.objects.filter(from=user)
You could also define a custom manager for the model and define these convenience methods there instead.
EDIT: Okay, technically it is possible, but I wouldn't recommend doing it. For a method, see Global Django Requests.
When you connect the set up a foreign key from Messages to Users, django automatically creates a related field on the User model instances. (see https://docs.djangoproject.com/en/dev/topics/db/queries/#backwards-related-objects)
So, if you have a user object, you should be able to access user_object.messages.all(). This feels like a more django-appropriate way to do what your get_authenticated_user_inbox_messages() method is doing.
I'm trying to selectively process a field in my Django/Python application based on whether a user is logged in or not. Basically, I have a model similar to the following:
class Resource(models.Model):
uploaded = models.DateTimeField()
name = models.CharField(max_length=200)
description = models.CharField(max_length=500, blank=True)
file = models.CharField(max_length=200)
What I want to do is for the file attribute to be set to one value if the user happens to be logged in (and has access to this resource based on a test against some permissions backend), and another value if the user is not logged in. So, when any client code tries to access Resource.file, it will get something like the following if the user is not logged in 'http://mysite.com/dummy_resource_for_people_without_access'. However, if the user is logged in and passes some tests for permissions, then the value of resource.file will actually be the true url of that resource (including any security keys etc. to access that resource).
From what I've read, it seems that you can only take account of the currently logged in user by passing that through the request context from a view function to the model. However, in the above use case I am trying to control the access more closely in the model without needing the client code to call a special function.
Your best bet is to create a function used to access the file attribute, and check there. In general, it would possible to turn the attribute into a descriptor which does it implicitly, but Django's metaclass magic would impede that.
In general however, Django is designed to handle authentication at the view-level (and does it very cleanly). If you need database layer authentication, consider a different setup, such as CouchDB.
Just in case anyone's interested, I solved the above issue by actually creating a custom model field in django that could then have a method that takes a user to generate a URI. So, in the database, I store a key to the resource as above in the file column. However, now the file column is some custom field:
class CustomFileField(models.CharField):
def to_python(self, value):
...
return CustomFileResource(value)
class CustomFileResource:
def __init__(self, *args, **kwargs):
....
def uri(usr):
#this method then gets the uri selectively based on the user .
The pattern above is nice because I can wrap the db field and then create a specific method for getting the uri based on who is trying to access it.
I know that you can prepopulate admin form fields based on other fields. For example, I have a slug field that is automatically populated based on the title field.
However, I would also like to make other automatic prepopulations based on the date. For example, I have an URL field, and I want it to automatically be set to http://example.com/20090209.mp3 where 20090209 is YYYYMMDD.
I would also like to have a text field that automatically starts with something like "Hello my name is author" where author is the current user's name. Of course, I also want the person to be able to edit the field. The point is to just make it so the user can fill out the admin form more easily, and not just to have fields that are completely automatic.
I know that you can prepopulate some values via GET, it will be something like this
http://localhost:8000/admin/app/model/add/?model_field=hello
I got some problems with date fields but, maybe this could help you.
I recently used Django's ModelAdmin.get_form method for this purpose.
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['my_field_name'].initial = 'abcd'
return form
Yout should be careful about side effects as you are manipulating the base_fields directly.
Django's built-in prepopulated_fields functionality is hardcoded to slugify, it can't really be used for more general purposes.
You'll need to write your own Javascript function to do the prepopulating. The best way to get it included in the admin page is to include it in the inner Media class of a custom Form or Widget. You'll then need to customize your ModelAdmin subclass to use the custom form or widget. Last, you'll need to render some inline Javascript along with each prepopulated field to register the onchange handler and tell it which other field to populate from; I would render this via the custom Widget. To make it nice and declarative you could use a custom ModelAdmin attribute (similar to prepopulated_fields), and override ModelAdmin.formfield_for_dbfield to create the widget and pass in the information about what field it should prepopulate from.
This kind of admin hacking is almost always possible, but (as you can tell from this convoluted summary) rarely simple, especially if you're making an effort to keep your code nicely encapsulated.
I tried a few of these answers and none of them worked. I simply wanted to prepulate a field with another field from a related model. Taking this answer as a starting point, I finally tried to manipulate the model instance object (here obj) directly and it worked for me.
class MyModelAdmin(models.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(MyModelAdmin, self).get_form(request, obj, **kwargs)
if not obj.some_model_field:
obj.some_model_field = obj.related_model.prepopulating_model_field
return form
You can override the default django admin field by replacing it with a form field of your choice.
Check this :
Add custom validation to the admin
I would also like to have a text field
that automatically starts with
something like "Hello my name is
author".
Check out the docs at: http://docs.djangoproject.com/en/dev/ref/models/fields/#default
You could have a CharField() or TextField() in your model, and set this option, which will set the default text. 'default' can also be a callable function.
Something like:
models.CharField(max_length=250, default="Default Text")
The slug handling is done with javascript.
So you have to override the templates in the admin and then populate the fields with javascript. The date thing should be trivial, but I dont know how you should get the logged in users name to the script (not that I have thought very hard but you get the drift :).