Django - on_rollback using transaction.atomic - django

What exactly I want to do
On rollback, I want to run a do_something function.
Is it possible to do so?
Right now, transaction.atomic does its job perfectly, but it only takes care of database. On rollback, I want to take care of a few other things using that do_something function I mentioned before.
My use cases for transaction.atomic are pretty simple and there's not much to say about, I just use it on typical views which are creating and updating objects.
*Updated
Example View
#transaction.atomic
def create(self, request, *args, **kwargs):
serializer = ConvertSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
instance = ConvertModel.objects.create(
request.user, request.converter, serializer.validated_data
)
# I have some code here which create a json file on the disk based on that instance
create_json(request.user, request.converter)
return Response(ConvertSerializer.data, status=HTTP_201_CREATED)
Example on_rollback function
This function simply removes the JSON file which was created by the view.
def remove_json(file_path):
os.remove(file_path)
The example is not what exactly I do now, but it's a pretty similar case. After the rollback, the database is okay, but I have that invalid JSON file on the disk which can make huge problems for me.

Related

DjangoListField analouge for an individual object resolver

I am using Django + GraphQL and it is extremely convenient to use DjangoListField. It allows me to override get_queryset at the ObjectType level and make sure all permissions verified there. In that manner I have a single place to have all my permission checks.
However, whenever I need to do something like:
contract = graphene.Field(ClientContractType,
pk=graphene.ID(required=True))
I have to also duplicate permissions validation in the resolve_contract method. I came up with the following solution to ensure permission and avoid duplication:
def resolve_contract(self, info, pk):
qs = ClientContract.objects.filter(pk=pk)
return ClientContractType.get_queryset(qs, info).get()
It works but I'd love to have some sort of DjangoObjectField which will encapsulate this for me and, potentially, pass arguments to the ClientContractType in some way. Have someone had this problem or knows a better solution?
The best I was able to come up with is to move this logic into the ObjectType class, defining a method (analogues to what DjangoObjectType already implemented)
#classmethod
def get_node(cls, info, id):
queryset = cls.get_queryset(cls._meta.model.objects, info)
try:
return queryset.get(pk=id)
except cls._meta.model.DoesNotExist:
return None
then, resolver will look like
def resolve_contract(self, info, pk):
return ClientContractType.get_node(pk)
Far from ideal, tho.

Django rollback transaction after perform_create

I am trying to use transaction.atomic with django without any luck I know I am doing something wrong but I dont know what.
class SnapshotView(BaseViewSet):
serializer_class = SnapshotSerializer
#transaction.atomic
def perform_create(self, serializer):
# this will call serializer.save()
snapshot = snapshot = super().perform_create(serializer)
# this will run some code can raise an exception
SnapshotServices.create_snapshot(snapshot=snapshot,
data=serializer.initial_data)
the first method that creates a new snapshot will pass the second will raise but still I can see my snapshot instance in the db, why is that?
I am in transaction block and something fails, is django not suppose to do a rollback?
the second method will throw a custom exception
I read the doc and it seems that I am doing everything right.
I figure it out.
my problem was that django uses default DB when using in atomic is None. Because I am using different DB, I just added using to my decorator
transaction.atomic(using=MYDB)
and that's fix my problem.

How to safely access request object in Django models

What I am trying to do:
I am trying to access request object in my django models so that I can get the currently logged in user with request.user.
What I have tried:
I found a hack on this site. But someone in the comments pointed out not to do it when in production.
I also tried to override model's __init__ method just like mentioned in this post. But I got an AttributeError: 'RelatedManager' object has no attribute 'request'
Models.py:
class TestManager(models.Manager):
def user_test(self):
return self.filter(user=self.request.user, viewed=False)
class Test(models.Model):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(Test, self).__init__(*args, **kwargs)
user = models.ForeignKey(User, related_name='test')
viewed = models.BooleanField(default=False)
objects = TestManager()
I trying to access request object in my Django models so that I can get the currently logged in user with request.user.
Well a problem is that models are not per se used in the context of a request. One for example frequently defines custom commands to do bookkeeping, or one can define an API where for example the user is not present. The idea of the Django approach is that models should not be request-aware. Models define the "business logic" layer: the models define entities and how they interact. By not respecting these layers, one makes the application vulnerable for a lot of problems.
The blog you refer to aims to create what they call a global state (which is a severe anti-patten): you save the request in the middleware when the view makes a call, such that you can then fetch that object in the model layer. There are some problems with this approach: first of all, like already said, not all use cases are views, and thus not all use cases pass through the middleware. It is thus possible that the attribute does not exist when fetching it.
Furthermore it is not guaranteed that the request object is indeed the request object of the view. It is for example possible that we use the model layer with a command that thus does not pass through the middleware, in which case we should use the previous view request (so potentially with a different user). If the server processes multiple requests concurrently, it is also possible that a view will see a request that arrived a few nanoseconds later, and thus again take the wrong user. It is also possible that the authentication middleware is conditional, and thus that not all requests have a user attribute. In short there are more than enough scenario's where this can fail, and the results can be severe: people seeing, editing, or deleting data that they do not "own" (have no permission to view, edit, or delete).
You thus will need to pass the request, or user object to the user_test method. For example with:
from django.http import HttpRequest
class TestManager(models.Manager):
def user_test(self, request_or_user):
if isinstance(request_or_user, HttpRequest):
return self.filter(user=request_or_user.user, viewed=False)
else:
return self.filter(user=request_or_user, viewed=False)
one thus has to pass the request object from the view to the function. Even this is not really pure. A real pure approach would only accept a user object:
class TestManager(models.Manager):
def user_test(self, user):
return self.filter(user=user, viewed=False)
So in a view one can use this as:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
# return Http response
For example if we want to render a template with this queryset, we can pass it like:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
return render(request, 'my_template.html', {'some_tests': some_tests})

Faking a Streaming Response in Django to Avoid Heroku Timeout

I have a Django app that uses django-wkhtmltopdf to generate PDFs on Heroku. Some of the responses exceed the 30 second timeout. Because this is a proof-of-concept running on the free tier, I'd prefer not to tear apart what I have to move to a worker/ poll process. My current view looks like this:
def dispatch(self, request, *args, **kwargs):
do_custom_stuff()
return super(MyViewClass, self).dispatch(request, *args, **kwargs)
Is there a way I can override the dispatch method of the view class to fake a streaming response like this or with the Empy Chunking approach mentioned here to send an empty response until the PDF is rendered? Sending an empty byte will restart the timeout process giving plenty of time to send the PDF.
I solved a similar problem using Celery, something like this.
def start_long_process_view(request, pk):
task = do_long_processing_stuff.delay()
return HttpResponse(f'{"task":"{task.id}"}')
Then you can have a second view that can check the task state.
from celery.result import AsyncResult
def check_long_process(request, task_id):
result = AsyncResult(task_id)
return HttpResponse(f'{"state":"{result.state}"')
Finally using javascript you can just fetch the status just after the task is being started. Updating every half second will more than enough to give your users a good feedback.
If you think Celery is to much, there are light alternatives that will work just great: https://djangopackages.org/grids/g/workers-queues-tasks/

overriding methods of ModelAdmin

I am working on a Django project where any time an admin does something in the admin console (CRUD), a set of people gets notified. I was pointed to three methods on ModelAdmin called log_addition, log_created and log_deleted which save all the necessary info into a special database called "django_admin_log".
I placed the following code into my admin.py:
class ModelAdmin(admin.ModelAdmin):
def log_addition(self, request, object):
subject = 'admin test of creation'
message = 'admin creation detected'
from_addr = 'no_reply#example.com'
recipient_list = ('luka#example.com',)
send_mail(subject, message, from_addr, recipient_list)
return super(ModelAdmin, self).log_addition( *args, **kwargs )
This code however, gets ignored when I create new users. Many posts actually recommend to create a different class name (MyModelAdmin) and I am not entirely sure why - the point is to override the existing model. I tried it, but with the same result. Can anyone point me in the right direction please? How exactly do you override a method of an existing class and give it some extra functionality?
Thank you!
Luka
EDIT: I figured it out, it seems that I had to unregister and re-register User for my change to work.
remove the return at the end.
If that doesn't work, you can instead put the code in a function called add_view:
class ModelAdmin(admin.ModelAdmin):
add_view(self, request):
...
super(ModelAdmin, self).add_view( *args, **kwargs)
This function can be overwritten to add functionality to the admin's view. If you look at the admin code:
https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/options.py#L923
you will see that the function you have tried to overwrite is called from within the add_view function so you could equally put the code here