Admin site uses default database in Django 1.6 - python

I have a project with several apps, each having its own database. I have a working routers.py to specify the correct database to use for each model. One of the app uses the django-admin subsite to manage its models.
This was all working fine in Django 1.5, but when moving to Django 1.6, I am not anymore able to edit my models: When I access the edit page of a model, I am getting an error "[SQL Server Native Client 11.0]Login timeout expired".
After some investigation, it seems like Django tries to connect to the database default which contains only dummy data in this project, as it should not be used anywhere.
Why does Django try to connect to the default database in 1.6, whereas it was working fine in 1.5? How can I fix this issue in 1.6?
[EDIT]
After some more research, I have found that Django uses the decorator #transaction.atomic for a few functions of its class ModelAdmin.
Looking at the code of this decorator, it seems like it's intended to support the possibility of specifying the database alias that needs to be used for this atomic transaction, with #transaction.atomic(using), and otherwise, if just called with #transaction.atomic as it is in the ModelAdmin Django class, it will use the DEFAULT_DB_ALIAS defined in django.db.utils.
I have tried unsuccessfully to override this behaviour and make these functions use the database alias I want.
Would you know of any way to do this?

I have finally found a way to force Django admin to use the database I want, but it's really hacky. If someone comes with a better solution, please do post it.
I have created a file named admin_helper.py where I redefine the methods that use the #transaction.atomic decorator in Django codebase, with exactly the same content, and using the decorator #transaction.atomic(DATABASE_ALIAS) which tells Django to use the correct database:
DATABASE_ALIAS = 'alias_of_the_database_i_want_to_use'
class ModelAdminCustom(admin.ModelAdmin):
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def add_view(self, request, form_url='', extra_context=None):
# exact same content as the method defined in django.contrib.admin.options.ModelAdmin
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def change_view(self, request, object_id, form_url='', extra_context=None):
# exact same content as the method defined in django.contrib.admin.options.ModelAdmin
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def delete_view(self, request, object_id, extra_context=None):
# exact same content as the method defined in django.contrib.admin.options.ModelAdmin
class MultiDBUserAdmin(UserAdmin):
#sensitive_post_parameters_m
#csrf_protect_m
#transaction.atomic(DATABASE_ALIAS)
def add_view(self, request, form_url='', extra_context=None):
# exact same content as the method defined in django.contrib.auth.admin.UserAdmin
# Unregister existing instance of UserAdmin for the User model, and register my own
try:
admin.site.unregister(User)
finally:
admin.site.register(User, MultiDBUserAdmin)
Then in my normal admin.py:
from myapp.admin_helper import ModelAdminCustom
class MyModelAdmin(ModelAdminCustom):
pass
admin.site.register(MyModel, MyModelAdmin)

Related

Custom permissions in Django

In Django Rest framework, we can verify permissions such as (isAuthenticated, isAdminUser...)
but how can we add our custom permissions and decide what django can do with those permissions?
I really want to understand what happens behind (I didn't find a documentation that explaint this):
#permission_classes([IsAdminUser])
Thank you
Write your own permissions, like this:
def permission_valid_token(func):
# first arg is the viewset, second is the request
def wrapper(*args, **kwargs):
valid_token, user_token = test_token_authorization(args[1].headers) # my function to validate the user
if not valid_token:
return Response(status=status.HTTP_401_UNAUTHORIZED)
return func(*args, **kwargs)
return wrapper
This is a permission that i'm using in app, probably you will have to change the valid_token part
And them you import in your views
from file_that_you_use.permissions import permission_valid_token
And you use as a decorator
class WeatherViewSet(viewsets.ViewSet):
#permission_valid_token
def list(self, request):
This is just a viewset for example, you can use generic viewsets or whatever you want.
If you are using VSCode, hover over #permission_classes([IsAdminUser]), click on Command (on your keyboard).
You can see what happens behind the scenes, play and create your custom Django version (not recommended) or you can overwrite the function.

django rest framework: order of decorators, auth classes, dispatch to be called

Very confused about the order of decorators, auth classes, dispatch to be called in djangorestframework. It seems that it is a little different from my django framework knowledge.
Some codes:
#operation_logger: customized decorator
class FileView(APIView):
parser_classes = (MultiPartParser,)#A
authentication_classes = (BasicAuthentication,)#B
#permission_classes((IsAuthenticated,))#C
#method_decorator(csrf_exempt)#D
#method_decorator(operation_logger)#E
def dispatch(self, request, *args, **kwargs):#F
return super(FileView, self).dispatch(request, *args, **kwargs)
#method_decorator(operation_logger)#G
def post(self, request):#H
print "xxxxpost"
What is the order of (A),B,C,D,E,F,G,H to be called when handling requests?
It seems that B is called after F but before G and H?
By the way, at beginning, my project was traditional django project. I know that request should go through all the middlewares. Now, I added a new app, which hosts APIs by DRF. I am not sure whether my request to APIs will go through all the middlewares or not?
Thanks
The call order is as you specified:
#method_decorator(csrf_exempt)
#method_decorator(operation_logger) (#E)
dispatch() calls initial() which calls check_permissions() which evaluates permission_classes (#B).
#method_decorator(operation_logger) (#G)
post()
One thing won't work, however:
#permission_classes((IsAuthenticated,)) on the method adds a permission_classes field to the callable (whatever that is) returned by (#E). This doesn't work with class-based views and is thus essentially a no-op.
Other parts have no fixed order, but are used on demand:
The authenticator is called whenever needed, i.e. when user or authentication information is accessed on the request object.
Same thing for parser_classes. These get passed to the request object and used lazily when request information is accessed, e.g. request.data.

Ensuring django template is never cached by any browser

I have a django template with a class-based DetailView associated to it. I've over-ridden the get_context_data method of the DetailView, using it to pass some required context variables that I display in the template (essentially an image). That's all I've done.
How do I ensure this particular django template of mine is never cached by any browser? Wading through various sources tells me I need to over-ride the HttpResponse in the dispatch method to accomplish no-cache?
I understand that I'll need to set the Cache-Control, Pragma, Expiry etc. I've just been unable to make headway regarding whether to (or how to) over-ride the dispatch method. Can someone give me a simple clarificational example of how they would go about implementing no-cache for such a template?
Firstly, it's the view rather than the template that you want to control HTTP caching on.
The template is just a chunk of HTML that can be rendered by any view, the view is what sends an HTTP Response to the web browser.
Django comes with some handy view decorators for controlling the HTTP headers returned by a view:
https://docs.djangoproject.com/en/1.9/topics/cache/#controlling-cache-using-other-headers
You can find this simple example in the docs:
from django.views.decorators.cache import never_cache
#never_cache
def myview(request):
# ...
If you are using 'class-based' views rather than simple functions then there's a gist here with an example of how to convert this decorator into a view Mixin class:
from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
class NeverCacheMixin(object):
#method_decorator(never_cache)
def dispatch(self, *args, **kwargs):
return super(NeverCacheMixin, self).dispatch(*args, **kwargs)
...which you'd use in your project like:
from django.views.generic.detail import DetailView
class ArticleView(NeverCacheMixin, DetailView):
template_name = "article_detail.html"
queryset = Article.objects.articles()
context_object_name = "article"

How can I PUT/POST JSON data to a ListSerializer?

I'm reading about customizing multiple update here and I haven't figured out in what case the custom ListSerializer update method is called. I would like to update multiple objects at once, I'm not worried about multiple create or delete at the moment.
From the example in the docs:
# serializers.py
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# custom update logic
...
class BookSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = BookListSerializer
And my ViewSet
# api.py
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
And my url setup using DefaultRouter
# urls.py
router = routers.DefaultRouter()
router.register(r'Book', BookViewSet)
urlpatterns = patterns('',
url(r'^api/', include(router.urls)),
...
So I have this set up using the DefaultRouter so that /api/Book/ will use the BookSerializer.
Is the general idea that if I POST/PUT/PATCH an array of JSON objects to /api/Book/ then the serializer should switch over to BookListSerializer?
I've tried POST/PUT/PATCH JSON data list to this /api/Book/ that looks like:
[ {id:1,title:thing1}, {id:2, title:thing2} ]
but it seems to still treat the data using BookSerializer instead of BookListSerializer. If I submit via POST I get Invalid data. Expected a dictionary, but got list. and if I submit via PATCH or PUT then I get a Method 'PATCH' not allowed error.
Question:
Do I have to adjust the allowed_methods of the DefaultRouter or the BookViewSet to allow POST/PATCH/PUT of lists? Are the generic views not set up to work with the ListSerializer?
I know I could write my own list deserializer for this, but I'm trying to stay up to date with the new features in DRF 3 and it looks like this should work but I'm just missing some convention or some option.
Django REST framework by default assumes that you are not dealing with bulk data creation, updates, or deletion. This is because 99% of people are not dealing with bulk data creation, and DRF leaves the other 1% to third-party libraries.
In Django REST framework 2.x and 3.x, a third party package exists for this.
Now, you are trying to do bulk creation but you are getting an error back that says
Invalid data. Expected a dictionary, but got list
This is because you are sending in a list of objects to create, instead of just sending in one. You can get around this a few ways, but the easiest is to just override get_serializer on your view to add the many=True flag to the serializer when it is a list.
def get_serializer(self, *args, **kwargs):
if "data" in kwargs:
data = kwargs["data"]
if isinstance(data, list):
kwargs["many"] = True
return super(MyViewSet, self).get_serializer(*args, **kwargs)
This will allow Django REST framework to know to automatically use the ListSerializer when creating objects in bulk. Now, for other operations such as updating and deleting, you are going to need to override the default routes. I'm going to assume that you are using the routes provided by Django REST framework bulk, but you are free to use whatever method names you want.
You are going to need to add methods for bulk PUT and PATCH to the view as well.
from rest_framework.response import Response
def bulk_update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
queryset = self.filter_queryset(self.get_queryset))
serializer = self.get_serializer(instance=queryset, data=request.data, many=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
def partial_bulk_update(self, *args, **kwargs):
kargs["partial"] = True
return super(MyView, self).bulk_update(*args, **kwargs)
This won't work out of the box as Django REST framework doesn't support bulk updates by default. This means you also have to implement your own bulk updates. The current code will handle bulk updates as though you are trying to update the entire list, which is how the old bulk updating package previously worked.
While you didn't ask for bulk deletion, that wouldn't be particularly difficult to do.
def bulk_delete(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
self.perform_delete(queryset)
return Response(status=204)
This has the same effect of removing all objects, the same as the old bulk plugin.
None of this code was tested. If it doesn't work, consider it as a detailed example.

How to make a model instance read-only after saving it once?

One of the functionalities in a Django project I am writing is sending a newsletter. I have a model, Newsletter and a function, send_newsletter, which I have registered to listen to Newsletter's post_save signal. When the newsletter object is saved via the admin interface, send_newsletter checks if created is True, and if yes it actually sends the mail.
However, it doesn't make much sense to edit a newsletter that has already been sent, for the obvious reasons. Is there a way of making the Newsletter object read-only once it has been saved?
Edit:
I know I can override the save method of the object to raise an error or do nothin if the object existed. However, I don't see the point of doing that. As for the former, I don't know where to catch that error and how to communicate the user the fact that the object wasn't saved. As for the latter, giving the user false feedback (the admin interface saying that the save succeded) doesn't seem like a Good Thing.
What I really want is allow the user to use the Admin interface to write the newsletter and send it, and then browse the newsletters that have already been sent. I would like the admin interface to show the data for sent newsletters in an non-editable input box, without the "Save" button. Alternatively I would like the "Save" button to be inactive.
You can check if it is creation or update in the model's save method:
def save(self, *args, **kwargs):
if self.pk:
raise StandardError('Can\'t modify bla bla bla.')
super(Payment, self).save(*args, **kwargs)
Code above will raise an exception if you try to save an existing object. Objects not previously persisted don't have their primary keys set.
Suggested reading: The Zen of Admin in chapter 17 of the Django Book.
Summary: The admin is not designed for what you're trying to do :(
However, the 1.0 version of the book covers only Django 0.96, and good things have happened since.
In Django 1.0, the admin site is more customizable. Since I haven't customized admin myself, I'll have to guess based on the docs, but I'd say overriding the model form is your best bet.
use readonlyadmin in ur amdin.py.List all the fields which u want to make readonly.After creating the object u canot edit them then
use the link
http://www.djangosnippets.org/snippets/937/
copy the file and then import in ur admin.py and used it
What you can easily do, is making all fields readonly:
class MyModelAdmin(ModelAdmin):
form = ...
def get_readonly_fields(self, request, obj=None):
if obj:
return MyModelAdmin.form.Meta.fields
else: # This is an addition
return []
As for making the Save disappear, it would be much easier if
has_change_permission returning False wouldnt disable even displaying the form
the code snippet responsible for rendering the admin form controls (the admin_modify.submit_row templatetag), wouldnt use show_save=True unconditionally.
Anyways, one way of making that guy not to be rendered :
Create an alternate version of has_change_permission, with proper logic:
class NoSaveModelAdminMixin(object):
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
response = super(NoSaveModelAdmin, self).render_change_form(request, context, add, change,form_url, obj)
response.context_data["has_change_permission"] = self.has_real_change_permission(request, obj)
def has_real_change_permission(self, request, obj):
return obj==None
def change_view(self, request, object_id, extra_context=None):
obj = self.get_object(request, unquote(object_id))
if not self.has_real_change_permission(request, obj) and request.method == 'POST':
raise PermissionDenied
return super(NoSaveModelAdmin, self).change_view(request, object_id, extra_context=extra_context)
Override the submit_row templatetag similar to this:
#admin_modify.register.inclusion_tag('admin/submit_line.html', takes_context=True)
def submit_row(context):
...
'show_save': context['has_change_permission']
...
admin_modify.submit_row = submit_row

Categories

Resources