How to do an Ajax form field on Flask Admin - flask

I'm building a Flask-Admin app that uses a REST API as its backend database, which I've implemented as my own BaseModelView. I've got it working with a custom model, and implemented all the functions to edit and save the model.
However I have one field, User, which needs to search another REST API endpoint for a User ID based on name/email, and as the list of users is expected to become quite large I want that field to be an AJAX lookup as I've seen in the SQLAlchemy example looking up a foreign key via Select2/Ajax.
As I need my own Ajax lookup, from following the docs and reading through the code, I'm struggling to find a working example of how to implement it myself and was wondering if anyone can direct me.
I've tried implementing an AjaxSelectField and using the form_ajax_refs property of BaseModelView to override the User field of the form (created in the scaffold_form method) without success.
Does anyone have an example of how I can put a custom Select2 Ajax lookup field, that looks up data from a REST API, on my model form so I can search for a User ID to assign to the model when I create/edit a model?

Figured it out finally, you have to add 2 things to your BaseModelView to do this:
In your BaseModelView class - Add an AjaxSelectField to your form with a custom model form:
def scaffold_form(self):
class AccountForm(Form):
owner = AjaxSelectField(UserAjaxModelLoader('owner'),
label='Owner', blank_text="Select User...")
return AccountForm
Then add a reference to your form_ajax_refs field:
form_ajax_refs = {
'owner': UserAjaxModelLoader('owner')
}
Finally the implementation of the UserAjaxModelLoader class which provides the AJAX endpoint and strings it all together:
from flask_admin.model.ajax import AjaxModelLoader, DEFAULT_PAGE_SIZE
class UserAjaxModelLoader(AjaxModelLoader):
def __init__(self, name, **options):
super(UserAjaxModelLoader, self).__init__(name, options)
def format(self, model):
if model:
return (model.uid, model.email)
return None
def get_one(self, pk):
return auth.get_user(pk)
def get_list(self, query, offset=0, limit=DEFAULT_PAGE_SIZE):
# Put your code to search REST API for users here
return users

Related

Getting fields from extra manager methods using django-rest-framework

I have the following custom model manager in Django that is meant to count the number of related comments and add them to the objects query set:
class PublicationManager(models.Manager):
def with_counts(self):
return self.annotate(
count_comments=Coalesce(models.Count('comment'), 0)
)
Adding this manager to the model does not automatically add the extra field in DRF. In my API view, I found a way to retrieve the count_comments field by overriding the get function such as:
class PublicationDetails(generics.RetrieveUpdateAPIView):
queryset = Publication.objects.with_counts()
...
def get(self, request, pk):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset.get(id=pk))
data = {**serializer.data}
data['count_comments'] = queryset.get(id=pk).count_comments
return Response(data)
This works for a single instance, but when I try to apply this to a paginated list view using pagination_class, overriding the get method seems to remove pagination functionality (i.e. I get a list of results instead of the usual page object with previous, next, etc.). This leads me to believe I'm doing something wrong: should I be adding the custom manager's extra field to the serializer instead? I'm not sure how to proceed given that I'm using a model serializer. Should I be using a basic serializer?
Update
As it turns out, I was using the model manager all wrong. I didn't understand the idea of table-level functionality when what I really wanted was row-level functionality to count the number of comments related to a single instance. I am now using a custom get_paginated_response method with Comment.objects.filter(publication=publication).count().
Original answer
I ended up solving this problem by creating a custom pagination class and overriding the get_paginated_response method.
class PaginationPublication(pagination.PageNumberPagination):
def get_paginated_response(self, data):
for item in data:
publication = Publication.objects.with_counts().get(id=item['id'])
item['count_comments'] = publication.count_comments
return super().get_paginated_response(data)
Not sure it's the most efficient solution, but it works!

How can I pass dictionary in Django Rest Framework?

In django, I was able to pass data using dictionary. Like I set the objects in my dictionary and pass it in return render and call the object in frontend (return render(request, 'c.html', context) right? so How can I do this in django rest?
You may return Response in rest framework like this if you are using django rest framework.
context = {'key':'value'}
return Response(context)
Or if you are using a serializer then
return Response(serializer.data)
In Django REST Framework the concept of Serializing is to convert DB data to a datatype that can be used by javascript. Every serializer comes with some field that is going to be processed. For example, if you have a class with the name Employee and its fields as Employee_id, Employee_name, is_admin, etc. Then, you would need AutoField, CharField, and BooleanField for storing and manipulating data through Django. Similarly, serializer also works with the same principle and has fields that are used to create a serializer.
DictField is basically a dictionary field that validates the input against a dictionary of objects. It has the following arguments:
child and allow_empty like this>>>
field_name = serializers.DictField(*args, **kwargs)
for example document = DictField(child=CharField())
you can use serializer like below>>>
from rest_framework import serializer
class Any(object):
def __init__(self, dictonary):
self.dict = dictionary
class AnySerializer(serializers.Serializer):
dictionary = serializers.DictField(
child = serializers.CharField())
you can visit similar problem for understanding through the real problem.
And this link is the complete documentation of your problem. You can check this out.

Django: How to obtain current user in CreateView and filter a ForeignKey select field based on this

I am creating an expense submission system, which will be multi-user.
For the purpose of this question, there are two models: Claim and Journey. A user creates a claim and each claim can have multiple journeys. I have made a gist of the code snippet as it's quite long.
In this snippet, I have sucessfully:
Made ClaimListView.get_queryset filter by current user, so whoever's logged in can only see a list of their own claims.
Made ClaimCreateView.form_valid set the correct user when the form is submitted.
Made ClaimDetailView.get_queryset filter by current user. If someone tries the url for another user's claim detail, they get a 404 (perfect!)
Done the same as above for JourneyListView
Done the same as above for JourneyDetailView - again 404 if not authroised :D
However, when I access JourneyCreateView via the URL, the dropdown box for claim still shows claims for the other users.
How should I filter the user within the JourneyCreateView class, so that the claim field only shows claims assigned to the current user?
The closest to a solution I've got is this answer which suggests overriding the __init__ function in the JourneyForm which would leave me with this:
class JourneyForm(forms.ModelForm):
class Meta:
model = Journey
fields = ['date', 'distance','claim']
def __init__(self,alloweduser,*args,**kwargs):
super (JourneyForm,self ).__init__(self,*args,**kwargs) # populates the post
self.fields['claim'].queryset = Claim.objects.filter(tech_id=alloweduser)
However I'm not sure how to pass the alloweduser in from JourneyCreateView or, more to the point, obtain the current user in this class.
form_valid isn't any use in this case, as I'm trying to obtain the user prior to the form being submitted.
In views, the request the view is handling is stored in self.request, so you can obtain the user with self.request.user, and its id with self.request.user.id.
A Django view with the FormMixin [Django-doc] has a method that can be overwritten to pass parameters: get_form_kwargs() [Django-doc].
So we can implement this as:
from django.views.generic.edit import CreateView
class JourneyCreateView(CreateView):
model = Journey
form_class = JourneyForm
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs(*args, **kwargs)
kwargs['alloweduser'] = self.request.user.id
return kwargs
# ...

Django rest framework hyperlinkrelatedfield for one table using primary key

I have a table called 'users' and 'location'. Users table has a foreign key that relates to location table. I have a users serializer to get the JSON. What would I do to get the hyperlinks for the users table using its primary key?
In django rest framework documentation, I couldn't find a solution. I tried using hyperlinkrelatedfield. But still I couldn't achieve this. Can someone help me in finding the solution?
Using rest-framework HyperlinkedRelatedField does not work because it was never built to expose the URL of the object being requested. Mainly because since the client already has the url of the user, why send it back again? Nevertheless you can achieve this by doing something like this.
class UserSerializer(serializers.ModelSerializer):
user_url = serializers.SerializerMethodField()
class Meta:
model = User
def get_label_location(self, obj):
return HyperlinkedRelatedField(view_name='user-detail',
read_only=True) \
.get_url(obj, view_name='label-detail',
request=self.context['request'], format=None)
Take note on a few things,
view-name param to the HyperlinkedRelatedField should be based on your url configuration
read-only has to be true since otherwise you'll have to specify the queryset. But since we have the object needed to generate the url we can ignore that.
I've set format param to None but you might want to set it based on your settings.
You can read up about SerializerMethodField here.

How to modify Django Rest Framework incoming request?

I am building a web app using Django that is pretty much only serving as the API server. I have a single-page application that connects to it as well as an Android client. I have a need to modify some of the incoming POST requests that are coming through.
My two use cases:
If during the registration process the user does not select an avatar image to upload (which is a simple TextField that is the URL to the image), I should be able to insert the default avatar URL. So something like if request.data["avatar"] is None: <use default>
The incoming "timestamp" requests from the Android client are all unix timestamps. I would like to convert this to Django's datetime on the fly - so, current request comes in with date_time = 1473387225, I'd like to convert that to a DateTime object.
Now, I'm already doing something similar for certain POST parameters. The way I do it right now is in the post() function of my generic ListCreateApiView I would directly modify the request object and then call the self.create() with that new request object. Is this the right way, or is there a much better way to do it?
Thanks!
If you are using django-rest-framework these things can be done by serializers.
For avatar use an URLField with default value.
For the timestamp you should probably create a custom field.
Check out this site: http://www.cdrf.co It is an easily navigable display of all the methods available on a given class. You can simply use this to overwrite the View you are using. If a model ViewSet, you likely want perform_create and perform_update.
I often do something like this:
class SomeViewSet(viewsets.ModelViewSet):
queryset = SomeModel.objects.all()
serializer_class = SomeModelSerializer
def perform_create(self, serializer):
data = self.request.data
# make some changes to self.request here
serializer.save(
#change some things here
field='some new value'
)
You can do this in a number of ways. As a part of your validation or in the to_internal_value of the request serializer or in a custom field serializer.
Heres an example of doing this as a part of a custom field serializer.
class AccountCreationSerializer(serializers.Serializer):
avatar = AvatarField(
required=False
allow_files=True
)
# Custom Field Serializer
class AvatarField(serializers.FilePathField):
def to_internal_value(self, value):
user_defined_path = super(AvatarField, self).to_internal_value(value)
if user_defined_path:
return user_defined_path
return default_path