Django Rest Framework DjangoModelPermissions not working properly - python-3.x

Why the DRF permission class DjangoModelPermissions allow every users to perform all requests like
POST, PUT, DELETE even those user i didn't manually assign them the add, change, delete permissions from my django admin. they are allowed to be only view the objects but why the are getting all unsafe request POST, DELETE... ?
Views.py
class HotelViewSet(viewsets.ModelViewSet):
queryset = Hotel.objects.all()
serializer_class = HotelSerializer
authentication_classes = [SessionAuthentication]
permission_classes = [DjangoModelPermissions]
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissions',
]
}

The viewsets.ModelViewSet does not implement delete(), post(), etc. methods itself. If you want to use it, you have to implement them yourself.
Instead, you should use Django REST generics. Please refer to Django REST framework docs:
https://www.django-rest-framework.org/api-guide/generic-views/

I would suggest using DRF generic views where you can use specific views like ListAPIView/ RetrieveAPIView for GET requests, CreateAPIView for POST, UpdateAPIView for PUT, etc.

Related

How to base queryset off of current user django rest serializer

I'm trying to create a serializer with DRF that is able to validate if a user has access to a primarykeyrelatedfield entry.
I have a separate function which returns a queryset of the files the user can access. All it needs as a parameter is the request object. I'd like to use this function as the queryset kwarg for the primarykeyrelatedfield.
However, I can't find a way to access "self" in this location, so there doesn't seem to be a way to define a Queryset which is dependent upon the current user for a serializer.
This is my current attempt, which fails since when calling _request(self) I cannot access self.
class MySerializer(serializers.Serializer):
def _request(self):
request = getattr(self.context, 'request', None)
if request:
return request
files = serializers.PrimaryKeyRelatedField(many=True, required=True, queryset=get_user_files(_request(self)))
I want to validate that the user has access to the file(s) they are referencing in the request. How would I do this?
I ended up settling on a slightly less clean answer than I'd have liked:
class MySerializer(serializers.Serializer):
files = serializers.PrimaryKeyRelatedField(many=True, required=True, queryset=ScanFile.objects.all())
def validate_files(self, value):
request = self.context.get('request')
queryset = get_user_files(request)
for file in value:
if not queryset.filter(pk=file.id).exists():
raise ValidationError({'Invalid file': file})
return value
This seems to be a bit inefficient, as it ends up querying for each file twice, but it achieves the affect of users can only access files they specifically have request to.

Saving a model with inlines in Django

This seems like a very easy problem, but I really can't figure out what's going on. I have some problems understanding the saving process on the Django admin site. This is the situation, simplified as much as possible:
models.py
import uuid
from django.conf import settings
from django.db import models
class BaseModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=settings.IS_DEV)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
class Order(BaseModel):
[various properties and functions]
class Article(BaseModel):
order = models.ForeignKey(Order, null=True, on_delete=models.CASCADE, related_name='articles')
[various properties and functions]
class PaymentOperation(BaseModel):
order = models.ForeignKey(Order, null=True, on_delete=models.CASCADE)
[various properties and functions]
admin.py
from django.conf import settings
from django.contrib import admin
from models import Order, Article, PaymentOperation
class BaseModelAdmin(admin.ModelAdmin):
readonly_fields = ['created', 'modified']
if not settings.IS_DEV:
readonly_fields.append('id')
class ArticleInline(admin.TabularInline):
fields = ['id', ...]
readonly_fields = ['id', ...]
can_delete = False
extra = 0
max_num = 0
[more code]
class PaymentOperationInline(admin.TabularInline):
fields = ['id', ...]
readonly_fields = ['id', ...]
can_delete = False
extra = 0
max_num = 0
[more code]
class OrderAdmin(BaseModelAdmin):
readonly_fields = BaseModelAdmin.readonly_fields + [...]
fieldsets = [...]
inlines = [ArticleInline, PaymentOperationInline]
[more code]
class ArticleAdmin(BaseModelAdmin):
readonly_fields = BaseModelAdmin.readonly_fields + [...]
fieldsets = [...]
[more code]
This is the main structure, but I'm not really sure if it's enough to generate the problem. I didn't want to clog the question with hundreds of lines of code. I'll try to be more specific if necessary.
Some fields in the Order and Article models are editable, while all fields in the PaymentOperation model are read-only.
If I edit an Article from its admin page, it works perfectly. If, on the other hand, I try to edit an Order, and then save it, the page behaves strangely. The order is not saved, and an error message appears on top of the page, saying "Please correct the errors below." All the fields, both editable and read-only, remain unchanged. The two inlines at the bottom of the page are in a worse state. All read-only fields are reset to the default value, if available, or nulled, and all IDs are different. Editable fields of the article inline remain the same. It looks to me like the page is trying to create new entries, instead of editing the old ones.
I tried commenting out the inlines in the OrderAdmin declaration one at a time, but with no effect. Commenting out both lets me save the order correctly.
I also tried adding a save_model and save_formset to OrderAdmin, but whatever the error is, it's produced before these functions are called. The shell where I run python manage.py runserver doesn't even show any error message.
There are many other models in models.py and in admin.py, and none of them has the same problem. Some of these models even have their own inlines.
I'm really puzzled. I'm trying to understand the difference between the part of the code shown above and the rest, but I can't find it. I know the saving process worked before I added the payment operation a few days ago, and I'm almost certain that I didn't change anything in the order and article models (I will check though, just to be sure). At this point I'm not sure I understood the saving process, or what's wrong with the code.
Edit: I can't find significant changes to the order and article models, as well as their admin counterparts.
Apparently, the whole problem is located in the editable=settings.IS_DEV setting for the ID of every model. I don't know exactly why, but setting editable=False solves the problem.
I noticed I never had this problem on the production server, only on the ones flagged as development. Fortunately being able to set a specific ID was only useful in a very limited set of cases, so I won't miss it, but I feel like I just traded a problem with a less annoying one. If someone has a better solution, I'll be happy to change my mind and recognize that as the answer to this question.

Flask sqlalchemy updating multiple fields in a row

Recently moved to flask from expressjs.
I am creating a flask app using flask flask-sqlalchemy flask-wtf
It is a form heavy application. I expect to have about 30-50 forms, with each form having 20-100 fields.
Client side forms are using flask-wtf
I am able to create models and able to create a crud functionality. The problem is that with each form I have to manually do
IN CREATE
[...]
# after validation
someItem = SomeModel(someField1=form.someField1.data, ..., somefieldN = form.someFieldN.data)
db.session.add(someItem)
db.session.commit()
IN UPDATE
[....]
queryItem = SomeModel.query.filter_by(id=item_id)
queryItem.somefield1 = form.someField1.data
[...]
queryItem.somefieldN = form.someFieldN.data
db.session.commit()
As apparent, with lots of forms, it gets very tedious. Is there a way to
If you are able to suggest a library that will do this
I have searched online for the last few hours. The closest I got to was to create a dictionary and then pass it like
someDict = {'someField1': form.someField1.data, ....}
SomeModel.query.filter_by(id=item.id).update(someDict)
As you can see it is equally tedious
I am hoping to find a way to pass the form data directly to SomeModel for creating as well as updating.
I previously used expressjs + knex and I was simply able to pass req.body after validation, to knex.
Thanks for your time
Use 'populate_obj' (note: model field names must match form fields)
Create record:
someItem = SomeModel()
form.populate_obj(someItem)
db.session.add(someItem)
db.session.commit()
Update record:
queryItem = SomeModel.query.filter_by(id=item_id)
form.populate_obj(queryItem)
db.session.commit()

Function service_account.Credentials.from_service_account_info() not working

I'm writing an application based on GCP services and I need to access to an external project. I stored on my Firestore database the authentication file's informations of the other project I need to access to. I read this documentation and I tried to apply it but my code does not work. As the documentaion says, what I pass to the authentication method is a dictionary[str, str].
This is my code:
from googleapiclient import discovery
from google.oauth2 import service_account
from google.cloud import firestore
project_id = body['project_id']
user = body['user']
snap_id = body['snapshot_id']
debuggee_id = body['debuggee_id']
db = firestore.Client()
ref = db.collection(u'users').document(user).collection(u'projects').document(project_id)
if ref.get().exists:
service_account_info = ref.get().to_dict()
else:
return None, 411
credentials = service_account.Credentials.from_service_account_info(
service_account_info,
scopes=['https://www.googleapis.com/auth/cloud-platform'])
service = discovery.build('clouddebugger', 'v2', credentials=credentials)
body is just a dictionary containing all the informations of the other project. What I can't understand is why this doesn't work and instead using the method from_service_account_file it works.
The following code will give to that method the same informations of the previous code, but inside a json file instead of a dictionary. Maybe the order of the elements is different, but I think that it doesn't matter at all.
credentials = service_account.Credentials.from_service_account_file(
[PATH_TO_PROJECT_KEY_FILE],
scopes=['https://www.googleapis.com/auth/cloud-platform'])
Can you tell me what I'm doing wrong with the method from_service_account_info?
Problem solved. When I posted the question I manually inserted from the GCP Firestore Console all the info about the other project. Then I wrote the code to make it authomatically and it worked. Honestly I don't know why it didn't worked before, the informations put inside Firestore were the same and the format as well.

Avoid having 2 identical entries in SQlAlchemy using Flask

I need to find a way to alert the user that what he's introducing already exists in the database, I have a Flask application and a SQLAlchemy database, I'm also using Flask-WTF,
I tried with a very precarious solution: I stored the data captured by the forms in variables and I was thinking of concatenating them and using a Query to search if they exist.
nombre1 = form.nombre_primero.data
nombre2 = form.nombre_segundo.data
Anyway I think this is not the most appropriate way to handle the situation.
does Flask has some way to do this? Or would you recommend me something?
I'd grateful if you could help me!
I would approach this by creating a composite unique constraint made of the select fields in the sqlalchemy model.
The table can be configured additionally via __table_args__ class property of the declarative base.
from app import db
from sqlalchemy import UniqueConstraint
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
nombre_primero = db.Column(db.String(64))
nombre_segundo = db.Column(db.String(64))
__table_args__ = (
UniqueConstraint('nombre_primero', 'nombre_segundo', name='uix_1'),
)
You can write the data to the table and handle what exception is raised when there is a conflict.
Okay, so there is a simple way to solve this, at the table itself, you make a condition that rejects duplicate entries based on some condition which you define.
So one easy way you can do this is make a hybrid function.
Read more about Hybrid Attributes here.
from sqlalchemy.ext.hybrid import hybrid_property
Now where you make the model for your table,
eg:
class xyz(db.Model):
__tablename__ = 'xyz'
#tablevalues defined here
#hybrid_property
def xyz()
#make a method here which rejects duplicate entries.
Once you read the documentation you will understand how this works.
I cant directly solve your problem because there isn't much information you have provided. But in this way, you can check the entries and make some method EASILY where your data is checked to be unique in anyway you want.

Resources