Django REST API custom methods for generic views - angularjs

I'm intern and work on a project where I develop DRF API that need to interact with mobile app written by my colleague with Ionic framework.
We are creating new user. My view method is following:
class NewUser(generics.CreateAPIView):
model = User
permission_classes = [permissions.AllowAny]
serializer_class = NewUserSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
token, created = Token.objects.get_or_create(user=serializer.instance)
return Response({'token': token.key}, status=status.HTTP_201_CREATED, headers=headers)
When someone wants to create new user via POST request if user name has't been taken yet, then API return 201 status code and token in JSON, if user name already taken it returns 400 status and error message in JSON. MY colleague request me to change status message to 200 when he tries to create username with name that already exist. He says that he can't consume the ERROR response.
His code looks like:
$http.post(url,{
username:$scope.tel,
password:$scope.passwd
}).success(function(data){
alert(data);
$ionicLoading.hide();
console.log(data);
})
Question:
1) Should I tweak my API to send 200 status instead of more logical 400 for 'user already register' error?
I tried to change my code, But i couldn't find the method to override in CreateAPIView/ModelSerializer of DRF. I ended up rewriting my view class to method:
#api_view(['POST'])
def newUser(request):
"""
Saves a new user on the database
"""
if request.method == 'POST':
serializer = NewUserSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
token, created = Token.objects.get_or_create(user=serializer.instance)
return Response({'token': token.key}, status=status.HTTP_201_CREATED, headers=serializer.data)
else:
return Response(serializer.errors, status=status.HTTP_200_OK)
Question:
2) If I want to change behaviorof API and responce, which method should I override
3) I'm new to Django and still don't qiute know where we should use generic views VS. #.... methods

200 vs 400 in this case is mostly preference. 400 means "Bad Request". This is generally more correct for a incorrectly formatted request, rather than one that doesn't meet some condition.
200 is just as appropriate it conveys the correct information:
Your request was valid, but I didn't create a new record.
As to how to do the override. The shortest path is to override the CreateAPIView.create and change the response code used. You should also avoid repeating the default behavior of CreateAPIView by calling super.
class CreateUserView(generics.CreateAPIView):
model = User
permission_classes = [permissions.AllowAny]
serializer_class = NewUserSerializer
def create(self, request, *args, **kwargs):
response = super(CreateUserView, self).create(request, *args, **kwargs)
token, created = Token.objects.get_or_create(user=serializer.instance)
response.status = status.HTTP_200_OK
response.data = {'token': token.key}
return response
Personally, I would have also crafted my NewUserSerializer to have a token field and handle the token so I didn't have to do that work in the View. It doesn't belong in a View.

Save and deletion hooks:
The following methods are provided by the mixin classes, and provide easy overriding of the object save or deletion behavior.
perform_create(self, serializer) - Called by CreateModelMixin when
saving a new object instance. perform_update(self, serializer) -
Called by UpdateModelMixin when saving an existing object instance.
perform_destroy(self, instance) - Called by DestroyModelMixin when
deleting an object instance.
These hooks are particularly useful for setting attributes that are implicit in the request, but are not part of the request data. For instance, you might set an attribute on the object based on the request user, or based on a URL keyword argument.
https://www.django-rest-framework.org/api-guide/generic-views/#methods
class CourseOrder(generics.CreateAPIView):
serializer_class = serializers.OrderCoursesSerializer
permission_classes = [permissions.AllowAny]
# hook before creating
def perform_create(self, serializer):
# print(serializer['name'].value)
# save post data
serializer.save()
try:
subject, from_email, to = 'New order', 'zelenchyks#gmail.com', 'zelenchyks#gmail.com'
text_content = 'New order'
html_content = '''
<p>client name: %s </p>
<p>client phone: %s </p>
'''
% (serializer['name'].value, serializer['mobile'].value)
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()
except Warning:
print('Huston we have a problems with smtp')

Related

How to create an object inside a GET request in a DRF APIview?

I am writing an API for a game using APIView for the view and I need to create a game round and game session object in the GET request. The goal is that when a user clicks on the game, a game session consisting of a few rounds is created every time. Let's say a game session only consists of one round.
How do I properly create those objects in the get method? Do I assign something to every field? Do I use the save or should I override the create() Method for both?
This what I have so far:
views.py
def get(self, request, *args, **kwargs):
controller = GameViewController()
gametype = Gametype.objects.all().filter(name="imageLabeler")
gametype_serializer = GametypeSerializer(gametype, many=True)
# while duration is not None:
random_resource = Resource.objects.all().order_by('?').first()
resource_serializer = ResourceSerializer(random_resource)
if request.user is None:
current_user = '1'
else:
current_user = request.user
current_score = 0
gamesession = Gamesession.save({
'id': controller.generate_random_id(Gamesession),
'user': current_user,
'gametype': gametype,
'created': datetime.now(),
})
gameround = Gameround.save({
'id': controller.generate_random_id(Gameround),
'user': current_user,
'gamesession': gamesession,
'created': datetime.now(),
'score': current_score,
})
return Response({'gametype': gametype_serializer.data,
'resource': resource_serializer.data
})
These are my models:
models.py
class Gamesession(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gametype = models.ForeignKey(Gametype, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
objects = models.Manager()
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
return super().save(*args, **kwargs)
class Gameround(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gamesession = models.ForeignKey(Gamesession, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
objects = models.Manager()
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
return super().save(*args, **kwargs)
I am writing an API for a game using APIView for the view and I need to create a game round and game session object in the GET request.
This is against the HTTP specifications. A GET request should not create, remove or update entities. This is specified under the safe methods section of the HTTP documentation [w3.org]:
In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested.
Methods that are allowed to have side-effects (update entities) are POST, PUT, PATCH, and DELETE.
This thus means that the browser should make POST calls to create a Gamesession for example. Therefore the standard APIViews defined by the Django REST framework, will never map an get method to logic that will create, update or remove items.

how do you get current request's in aiohttp when it isn't a handler

I have an aiohttp app that has some endpoints created using nested apps.
My use case is once the request is processed, I want to return not in web.response format but whatever format is requested by the client in their request's header (could be csv, json, html etc).
So I was using a decorator and from that decorator wrapper, getting the current request to know the header format and process the response to that type.
My question is how can I get current request's context. I know there isn't anything like current_app like in flask, so what's the best of doing what I want to do.
Below I am posting some code that explains above content:
#subapp_routes.get('')
#subapp_routes.get('/{c_id}')
#format_output
async def index(request):
print(request)
c_id = request.match_info.get('c_id', None)
return await get_index(c_id)
def format_data_object(data):
status = 200
mime = _most_acceptable_format(request, data) # HOW TO PASS THIS CURRENT request AS CURRENTLY THIS ISN'T GETTING RECOGNIZED. I TRIED aiohttp.request and aiohttp.web.request but get not recognized, so not sure now
if mime == MIME_DATAFRAME:
return _render_dataframe(data, status)
elif mime == MIME_CSV:
return _render_csv(data, status)
elif mime == MIME_JSON:
return _render_json(data, status)
elif mime == MIME_HTML:
return _render_html(data, status)
raise InvalidRequest('unrecognized format: "%s"' % mime)
def format_output(function):
"""
Output format decorator.
"""
#wraps(function)
def wrapper(*args, **kwargs):
try:
data = function(*args, **kwargs)
return format_data_object(data)
except Exception as ex:
return handle_error(ex)
return wrapper
flask way spoils people.
If you need an entire request, DB connection or other resource -- explicitly pass it into called function.
Very obvious and elegant way which doesn't require any implicit context namespace magic.
Please left things like threadlocal variables to system tools, user code should not use them for sake of simplicity and readability.

Django generic UpdateView in Djangular

The Python source code for Djangular Demos gives examples of how to process the post request from a form that creates a Django object instance. But they don't show how to process the post request from a form which updates an existing object instance.
The code for updating an object seems rather complicated: my code is missing something crucial. Using my code I always get a form validation error: Object with this Name already exists.
I am using the Django generic UpdateView class and my model has a unique field called name.
My code:
from django.views.generic.edit import UpdateView
class MyForm(NgModelFormMixin, Bootstrap3FormMixin, NgModelForm):
scope_prefix='form_data'
form_name = 'my_form'
class Meta:
model = models.MyModel
fields = ['name','person']
class MyModelUpdate(UpdateView):
model = models.MyModel
form_class = MyForm
def post(self, request, **kwargs):
if request.is_ajax():
return self.ajax(request, **kwargs)
return super(MyModelUpdate, self).post(request, **kwargs)
# from the djangular combined_validation example
def ajax(self, request, **kwargs):
# tbd: need update-specific logic here: pass in instance
# parameter (object) or set it from pk. Base class post
# methods use self.get_object()
form = self.form_class(data=json.loads(request.body))
return JsonResponse({'errors': form.errors,
'success_url': force_text(self.success_url)})
What code do I need to get Django to load the instance identified by the pk argument and attach it to the form. That would be the default behavior when the request data comes from POST rather than ajax?
After trial and error experimentation I came up with the following new implementation for the view's ajax method. It passes my tests but feels clunky.
def ajax(self, request, **kwargs):
form = self.form_class(data=json.loads(request.body),
instance=self.get_object())
try:
form.save()
except:
# error is in form.errors
pass
return JsonResponse({'errors': form.errors,
'success_url': force_text(self.success_url)})

Passing credentials to a task

Hopefully someone can help. I am building an app on Google app engine and trying to pass the credentials of an authenticated user to the push task Handler. I am using the OAuth2DecoratorFromClientSecrets library to create the decorator which seems to store the credentials for the user in the datastore. It stores it with a key name of something like "110111913122157971566". My problem is that I cant seem to find a way to figure out what that key name is so that I can retrieve it using the StorageByKeyName method from within my worker handler. The documentation I have read uses the user_id as the key name but this doesnt work for me as the credentials are not stored with the user_id as the key name, however if I hard code the key name then the code does work. I am aware that I could run the copy code from within the Submit handler but need to run it as a separate task. Below is a sample of my code, thanks for any help you can provide:
JINJA_ENVIRONMENT = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
extensions=['jinja2.ext.autoescape'],
autoescape=True)
SCOPES =['https://www.googleapis.com/auth/drive']
decorator = OAuth2DecoratorFromClientSecrets(
os.path.join(os.path.dirname(__file__),
'client_secrets.json'),
' '.join(SCOPES)
)
class MainPage(webapp2.RequestHandler):
def get(self):
if users.get_current_user():
url = users.create_logout_url(self.request.uri)
url_linktext = 'Logout'
template_values = {'url': url,
'url_linktext': url_linktext,
}
template = JINJA_ENVIRONMENT.get_template('index.html')
self.response.write(template.render(template_values))
else:
self.redirect(users.create_login_url(self.request.uri))
class Submit(webapp2.RequestHandler):
#decorator.oauth_required
def post(self):
taskqueue.add(url='/worker', params={'user_id' : users.get_current_user().user_id()})
self.response.write('<html><body>You wrote:<pre>')
self.response.write(users.get_current_user().user_id())
self.response.write('</pre></body></html>')
class Worker(webapp2.RequestHandler):
def post(self):
user_id = self.request.get('user_id')
credentials = StorageByKeyName(CredentialsModel, user_id , 'credentials').get()
http = httplib2.Http()
http = credentials.authorize(http)
service = build('drive', 'v2',http=http)
fileId = 'actual file_id of drive file here'
copied_file = {'title': 'My New Test Doc2'}
new_file = service.files().copy(fileId=fileId,body=copied_file).execute(http=http)
application = webapp2.WSGIApplication([
('/', MainPage),
('/submit', Submit),
('/worker', Worker),
(decorator.callback_path, decorator.callback_handler()),
], debug=True)
First, remove the http=decorator.http at the top level - it doesn't appear to be used and should only be used from inside the decorated method.
I'm not certain, but looking at the decorator code I think it is is keying based on the user_id(). Your code is displaying that, but the parameter you are passing to the task isn't. Try {'user_id' : users.get_current_user().user_id()}.

GAE put object, redirect, then query for object returns null?

All,
I am in the process of learning Google App Engine / Webapp2 and i'm having trouble saving an object to the datastore, redirecting to another page/handler, then fetching that object from the datastore. Forgive me if there is an easy answer to this question. The following is a description of the code I have.
I have a base handler:
class BaseHandler(webapp2.RequestHandler):
def set_secure_cookie(self, name, val):
cookie_val = make_secure_val(val)
self.response.headers.add_header(
'Set-Cookie',
'%s=%s; Path=/' % (name, cookie_val))
def get_secure_cookie(self, name):
cookie_val = self.request.cookies.get(name)
return cookie_val and check_secure_val(cookie_val)
def login(self, user):
self.set_secure_cookie('user', str(user.name))
# Called before every request and stores user object
def initialize(self, *a, **kw):
webapp2.RequestHandler.initialize(self, *a, **kw)
username = self.get_secure_cookie('user')
self.user = username and User.by_name(str(username))
I have a Signup page which inherits from BaseHandler:
class Signup(BaseHandler):
def get(self):
# Get the page
def post(self):
has_error = False
# Extract and validate the input
if has_error:
#Re-render the form
else:
new_user = User.register(self.username, self.password, self.email)
new_user.put()
self.login(new_user)
self.redirect("/blog/welcome")
If the user is a new user, the User db.Model object is created, the user is stored to the datastore, a user cookie is set and we are redirected to the Welcome handler:
class Welcome(BaseHandler):
def get(self):
if self.user:
self.render('welcome.html', username = self.user.name)
else:
self.redirect('/blog/signup')
The intent here is that upon redirect, BaseHandler.initialize() would get called and would set self.user of the new user I just created.
Here is what I know:
- When signing up a new user, I am redirected back to the signup page.
- If I then manually navigate to /blog/welcome, the page loads correctly with the new username populated.
If I add the following logging statements into Welcome.get():
username = self.get_secure_cookie('user')
logging.info("Cookie %r obtained inside of Welcome.get().", username)
logging.info("Found user %r", User.by_name(str(username)))
The cookie is obtained for the new username but no User object is found. Again, if I navigate directly to /blog/welcome, the logs report that the cookie is obtained and the User object is found for the new user.
The User object looks like so:
def users_key(group = 'default'):
return db.Key.from_path('users', group)
class User(db.Model):
name = db.StringProperty(required = True)
password = db.StringProperty(required = True)
email = db.StringProperty()
#classmethod
def by_name(cls, name):
u = User.all().filter('name =', name).get()
return u
#classmethod
def register(cls, name, password, email = None):
return User(parent = users_key(),
name = name,
password = password,
email = email)
Is there something about the datastore that is causing this first query to get the new user to return nothing? How should I proceed in debugging this? Is there additional reading I should do? (I have tried to provide all necessary code snippets but I can provide additional code if required.)
Just guessing, but I suspect self.username, self.password and self.email in your RequestHandler are not set to anything. I'm assuming you're getting those paramters from the request POST data, but that's not happening in the code shown.
The other potential problem is that your query is eventually consistent, and may not reflect recent changes (ie new User entity). It would be much better if you fetch the user by it's key or id with a get() call instead of a query via filter().

Resources