Tastypie, add element to a many to many relationship - django

I'm building a django tastypie api, and I have a problem with adding elements in ManyToMany relationships
Example,
models.py
class Picture(models.db):
""" A picture of people"""
people = models.ManyToManyField(Person, related_name='pictures',
help_text="The people in this picture",
)
class Person(models.db):
""" A model to represet a person """
name = models.CharField(max_length=200,
help_text="The name of this person",
)
resources:
class PictureResource(ModelResource):
""" API Resource for the Picture model """
people = fields.ToManyField(PersonResource, 'people', null=True,
related_name="pictures", help_text="The people in this picture",
)
class PersonResource(ModelResource):
""" API Resource for the Person model """
pictures = fields.ToManyField(PictureResource, 'pictures', null=True,
related_name="people", help_text="The pictures were this person appears",
)
My problem is that I would like to have an add_person end point in my picture resource.
If I use PUT, then I need to specify all the data in the picture
If I use PATCH, I still need to specify all the people in the picture.
Of course I could simply generate the /api/picture/:id/add_people URL and there I could handle my problem. The problem with that is that it does not feel clean.
Another solution would be to generate the /api/picture/:id/people end point, and there I could do GET, POST, PUT, like it's a new resource, but I don't know how to implement this and it seems strange to create new people under this resource.
Any thoughts?

I implemented this by overriding the save_m2m function of the API Resource. Here is an example using your models.
def save_m2m(self, bundle):
for field_name, field_object in self.fields.items():
if not getattr(field_object, 'is_m2m', False):
continue
if not field_object.attribute:
continue
if field_object.readonly:
continue
# Get the manager.
related_mngr = getattr(bundle.obj, field_object.attribute)
# This is code commented out from the original function
# that would clear out the existing related "Person" objects
#if hasattr(related_mngr, 'clear'):
# Clear it out, just to be safe.
#related_mngr.clear()
related_objs = []
for related_bundle in bundle.data[field_name]:
# See if this person already exists in the database
try:
person = Person.objects.get(name=related_bundle.obj.name)
# If it doesn't exist, then save and use the object TastyPie
# has already prepared for creation
except Person.DoesNotExist:
person = related_bundle.obj
person.save()
related_objs.append(person)
related_mngr.add(*related_objs)

Related

Show cutomize message in Django Admin on delete of a record

I am trying to change the default error message of django admin to my own message app that should display like a normal message system
here is my model for the app
class Role(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(null=True, unique=True, blank=False)
def __str__(self):
return self.name
in on other model this model become the forighn key. here is the second model
class LearningPath(models.Model):
Path_Name = models.CharField(max_length=500)
Role = models.ForeignKey(Role, on_delete=models.DO_NOTHING)
after that, I have created some roles and then some Learning Paths.
the issue is that I when I delete any of Role that is used inside any of Learning Path it shows me the below error
what I want is to show the error message in a normal message app as the normal alert div appear. I have tried to write some code in admin.py but it's not working.
here is admin.py code
class RoleAdmin(admin.ModelAdmin):
list_display = ('name',)
prepopulated_fields = {'slug': ('name',)}
list_per_page = 20
def delete_model(self, request, obj):
try:
obj.delete()
except IntegrityError:
messages.error(request, 'This object can not be deleted!')
admin.site.register(Role, RoleAdmin)
Please don't use on_delete=models.DO_NOTHING [Django-doc]. It relies on the fact that the database will somehow deal with it. But that behavior can be unpredictable. Some database can simply ignore this, other might prevent deleting the objects, or fail silently.
It might be better to use PROTECT [Django-doc]. This means that Django will identify the problem itself, and simply prevent querying the database in the first place to remove the object.
Django admin seems to take this into account as well. As we can read in the documentation of the get_deleted_objects(…) method:
(…)
This method must return a 4-tuple of (deleted_objects,
model_count, perms_needed, protected).
(…)
protected is a list of strings representing of all the protected related objects that can’t be deleted. The list is displayed in the template.
thanks to the Willem Van Onsem comment, my issue got fixed.
i have used on_delete=models.PROTECT into my model and it did work what i was looking for

Filter M2M in template?

In my model, I have the following M2M field
class FamilyMember(AbstractUser):
...
email_list = models.ManyToManyField('EmailList', verbose_name="Email Lists", blank=True, null=True)
...
The EmailList table looks like this:
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
In the app, the user should only see records that is_active=True and is_managed_by_user=True.
In the Admin side, the admin should be able to add a user to any/all of these groups, regardless of the is_active and is_managed_by_user flag.
What happens is that the Admin assigns a user to all of the email list records. Then, the user logs in and can only see a subset of the list (is_active=True and is_managed_by_user=True). This is expected behavior. However, what comes next is not.
The user deselects an email list item and then saves the record. Since M2M_Save first clears all of the m2m records before it calls save() I lose all of the records that the Admin assigned to this user.
How can I keep those? I've tried creating multiple lists and then merging them before the save, I've tried passing the entire list to the template and then hiding the ones where is_managed_by_user=False, and I just can't get anything to work.
What makes this even more tricky for me is that this is all wrapped up in a formset.
How would you go about coding this? What is the right way to do it? Do I filter out the records that the user shouldn't see in my view? If so, how do I merge those missing records before I save any changes that the user makes?
You might want to try setting up a model manager in your models.py to take care of the filtering. You can then call the filter in your views.py like so:
models.py:
class EmailListQuerySet(models.query.QuerySet):
def active(self):
return self.filter(is_active=True)
def managed_by_user(self):
return self.filter(is_managed_by_user=True)
class EmailListManager(models.Manager):
def get_queryset(self):
return EmailListQuerySet(self.model, using=self._db)
def get_active(self):
return self.get_queryset().active()
def get_all(self):
return self.get_queryset().active().managed_by_user()
class EmailList(models.Model):
name = models.CharField(max_length=50, default='My List')
description = models.TextField(blank=True)
is_active = models.BooleanField(verbose_name="Active")
is_managed_by_user = models.BooleanField(verbose_name="User Managed")
objects = EmailListManager()
views.py:
def view(request):
email = EmailList.objects.get_all()
return render(request, 'template.html', {'email': email})
Obviously there is outstanding data incorporated in my example, and you are more than welcome to change the variables/filters according to your needs. However, I hope the above can give you an idea of the possibilities you can try.
In your views you could do email = EmailList.objects.all().is_active().is_managed_by_user(), but the loading time will be longer if you have a lot of objects in your database. The model manager is preferred to save memory. Additionally, it is not reliant on what the user does, so both the admin and user interface have to talk to the model directly (keeping them in sync).
Note: The example above is typed directly into this answer and has not been validated in a text editor. I apologize if there are some syntax or typo errors.

Tastypie accessing fields from inherited models

Is it possible to include fields on related models, using tastypie?
As per my models below: if I persist one VideoContent and one TextContent instance to the DB, I can then get 2 objects back from my Content resource, however none of the additional fields are available.
Is it possible to include fields from related models (in this instance, the video url and the text content) and will that cater for adding more Content types in the future without having to rewrite the Content Resource, or am I coming at this from the wrong direction?
The goal is to be able to extend this with more ContentTypes without having to make changes to the Content resource (assuming it's possible to get it working in the first place)
Models.py:
class Content(models.Model):
parent = models.ForeignKey('Content', related_name='children', null=True, blank=True)
class TextContent(Content):
text = models.CharField(max_length=100)
class VideoContent(Content):
url = models.CharField(max_length=1000)
And then my resources:
class ContentResource(ModelResource):
children = fields.ToManyField('myapp.api.resources.ContentResource', 'children', null=True, full=True)
class Meta:
resource_name = 'content'
queryset = ContentResource.objects.all()
authorization = Authorization()
always_return_data = True
I found a good solution in another answer
Populating a tastypie resource for a multi-table inheritance Django model
I've run into the same problem - although I'm still in the middle of solving it. Two things that I've figured out so far:
django-model-utils provides an inheritence manager that lets you use the abstract base class to query it's table and can automatically downcast the query results.
One thing to look at is the dehydrate/rehydrate methods available to Resource classes.
This is what I did:
class CommandResource(ModelResource):
class Meta:
queryset = Command.objects.select_subclasses().all()
That only gets you half way - the resource must also include the dehydrate/rehydrate stuff because you have to manually package the object up for transmission (or recieving) from the user.
The thing I'm realizing now is that this is super hacky and there's gotta be a better/cleaner way provided by tastypie - they can't expect you to have to do this type of manual repackaging in these types of situations - but, maybe they do. I've only got about 8 hours of experience with tastypie # this point so if I'm explaining this all wrong perhaps some nice stackoverflow user can set me straight. :D :D :D
I had the same requirement and finally solved it.
I didn't like the answer given in the above link because I didn't like the idea of combining queryset and re-sorting.
Apparently, you can inherit multiple resources.
By subclassing multiple resources, you include the fields of the resources.
And since those fields are unique to each resource, I made them nullable in the init.
wonder if there's a way to list the parents only once. (There are two now. One for subclassing, and one in meta)
class SudaThreadResource(ThreadResource):
def __init__(self, *args, **kwargs):
super(SudaThreadResource, self).__init__(*args, **kwargs)
for field_name, field_object in self.fields.items():
# inherited_fields can be null
if field_name in self.Meta.inherited_fields:
field_object.null=True
class Meta(ThreadResource.Meta):
resource_name = 'thread_suda'
usedgoodthread_fields = UsedgoodThreadResource.Meta.fields[:]
userdiscountinfothread_fields = UserDiscountinfoThreadResource.Meta.fields[:]
staffdiscountinfothread_fields = StaffDiscountinfoThreadResource.Meta.fields[:]
bitem_checklistthread_fields = BitemChecklistThreadResource.Meta.fields[:]
parent_field_set = set(ThreadResource.Meta.fields[:])
field_set = set(
set(usedgoodthread_fields) |
set(userdiscountinfothread_fields) |
set(staffdiscountinfothread_fields) |
set(bitem_checklistthread_fields)
)
fields = list(field_set)
inherited_fields = list(field_set - parent_field_set)
queryset = forum_models.Thread.objects.not_deleted().exclude(
thread_type__in=(forum_const.THREAD_TYPE_MOMSDIARY, forum_const.THREAD_TYPE_SOCIAL_DISCOUNTINFO)
).select_subclasses()

django admin add data with fixed value in some field

class Facilites(models.Model):
id = models.CharField(max_length=32, primary_key=True)
name = models.CharField(max_length=128)
class Objects(models.Model):
name = models.CharField(max_length=64)
facilityid = models.ForeignKey(Facilities)
class Admins(models.Model):
user = models.OneToOneField(User)
facilities = models.ManyToManyField(Facilities)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Admins.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What i want is to have users (admins) only be able to add or modify "facilityid" in Objects to values specified in their Admins.facilities.
So if some user is named UserA and has facilities = ('FacA', 'FacB'), when he is adding a new object to DB, he shoudln't be able to add something like Object('Random object', 'FacC')
Also, he shouldn't be able to modify existing objects to facilities he doesn't belong to.
I have filtered the Objects with:
def queryset(self, request):
qs = super(ObjectsAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(facitityid__id__in = request.user.get_profile().facilities.all())
so users can only see the object that belong to their facilities. But i have no idea how to prevent them from adding/editing object out of their facilities.
edit:
found the answer here: https://stackoverflow.com/a/3048563/1421572
It turns out that ModelAdmin.formfield_for_foreignkey was the right answer in this situation: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I would do this with either a pre-made facility list (i.e. You could create an integer field that is hooked to FACILITY_CHOICES for the user to select from.)
If only admins can do it then permissions sounds quite viable. You can also do form validation to check for errors against the db. Depending on how many facilities you have you may want a different approach.
You can do this same technique with a models.CharField as well. So perhaps assign a 3 letter facility code to each facility and require the entry to match one of the 3 letter strings. You could even have the list in a .txt file to read from. There are really so many ways to do this. I will provide an example of a pre-made facility list and accessing the facility a particular user belongs to from the api / template:
NYC_FACILITY = 0
LA_FACILITY = 1
ATL_FACILITY = 2
FACILITY_CHOICES = (
(NYC_FACILITY, 'NYC'),
(LA_FACILITY, 'LA'),
(ATL_FACILITY, 'ATL'),
class Facility(models.Model):
name = models.IntegerField(choices=FACILITY_CHOICES, default="NYC")
class Meta:
order_by = ['name']
verbose_name_plural = "facilities"
verbose_name = "facility"
def __unicode__(self):
return self.name
As far as viewing the facilities page that a particular user belongs to you will have a m2m one to one or FK relationship between the objects. If FK or m2m relationship then you will have access to additional methods of that model type. get_related However, I'm not going to use get_related in my example. Once you are in an instance you then have access to entry_set.
# models.py
from django.auth import User
class Person(User):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
facility_loc = models.ForeignKey('Facility') # ForeignKey used assuming only one person can belong to a facility.
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return "/%s/%s/" % self.facility_loc % self.slug
# views.py - TemplateView is automatically given a context variable called params which parses data from the URL. So, I'll leave the regex in the URLConf up to you.
class UserFacilityView(TemplateView):
model = Facility
template_name = "user_facility.html"
Now in your template you should be able to access facility_set from a User instance or user_set from a facility instance.

Django Models Manager for saving custom processed data in a field

A noob here. I have a model class where I want to save something processed in one of the fields of that table. am trying to use a ModelManager for that but do not know if it is possible or how to.
I want to save a custom url for each post here. So I want to have a method in PostManager class which will calculate hash of something (say current time) and save it as a url. I could not find any syntax help so asking it here.
class Post (models.Model):
name = models.CharField(max_length=1000, help_text="required, name of the post")
description = models.TextField(blank=True)
created_datetime = models.DateTimeField(auto_now_add=True, editable=False)
modified_datetime = models.DateTimeField(auto_now=True, editable=False)
custom_hashed_url = models.CharField(unique=True, max_length=1000, editable=False)
def save(self, *args, **kwargs):
#How to refer to the custom_hashed_url in the Post class?
super(Model, self).save()
If you want the url to be saved in the database with the rest of the information, it will need to appear in the model as a field.
Change the url to an appropriate field type and set its 'editable' attribute to False, as you've done with the datetime fields. This will stop it appearing in forms.
You could then override the model's save method (see Django docs) so that it calculates the post's url and adds it automatically as the instance is saved!
Model managers are used for 'model level' interactions that work with many instances, or sets of instances. In this case you are trying to manipulate a single instance. We use a field to store the information in the database for the record and a method (in this case overriding a built-in method to hook into the default behaviours) to calculate the field's value.
Good luck!