I'm building a single database/shared schema multi-tenant application using Django 2.2 and Python 3.7.
I'm attempting to use the new contextvars api to share the tenant state (an Organization) between views.
I'm setting the state in a custom middleware like this:
# tenant_middleware.py
from organization.models import Organization
import contextvars
import tenant.models as tenant_model
tenant = contextvars.ContextVar('tenant', default=None)
class TenantMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
user = request.user
if user.is_authenticated:
organization = Organization.objects.get(organizationuser__is_current_organization=True, organizationuser__user=user)
tenant_object = tenant_model.Tenant.objects.get(organization=organization)
tenant.set(tenant_object)
return response
I'm using this state by having my app's models inherit from a TenantAwareModel like this:
# tenant_models.py
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from organization.models import Organization
from tenant_middleware import tenant
User = get_user_model()
class TenantManager(models.Manager):
def get_queryset(self, *args, **kwargs):
tenant_object = tenant.get()
if tenant_object:
return super(TenantManager, self).get_queryset(*args, **kwargs).filter(tenant=tenant_object)
else:
return None
#receiver(pre_save)
def pre_save_callback(sender, instance, **kwargs):
tenant_object = tenant.get()
instance.tenant = tenant_object
class Tenant(models.Model):
organization = models.ForeignKey(Organization, null=False, on_delete=models.CASCADE)
def __str__(self):
return self.organization.name
class TenantAwareModel(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='%(app_label)s_%(class)s_related', related_query_name='%(app_label)s_%(class)ss')
objects = models.Manager()
tenant_objects = TenantManager()
class Meta:
abstract = True
In my application the business logic can then retrieve querysets using .tenant_objects... on a model class rather than .objects...
The problem I'm having is that it doesn't always work - specifically in these cases:
In my login view after login() is called, the middleware runs and I can see the tenant is set correctly. When I redirect from my login view to my home view, however, the state is (initially) empty again and seems to get set properly after the home view executes. If I reload the home view, everything works fine.
If I logout and then login again as a different user, the state from the previous user is retained, again until a do a reload of the page. This seems related to the previous issue, as it almost seems like the state is lagging (for lack of a better word).
I use Celery to spin off shared_tasks for processing. I have to manually pass the tenant to these, as they don't pick up the context.
Questions:
Am I doing this correctly?
Do I need to manually reload the state somehow in each module?
Frustrated, as I can find almost no examples of doing this and very little discussion of contextvars. I'm trying to avoid passing the tenant around manually everywhere or using thread.locals.
Thanks.
You're only setting the context after the response has been generated. That means it will always lag. You probably want to set it before, then check after if the user has changed.
Note though that I'm not really sure this will ever work exactly how you want. Context vars are by definition local; but in an environment like Django you can never guarantee that consecutive requests from the same user will be served by the same server process, and similarly one process can serve requests from multiple users. Plus, as you've noted, Celery is a yet another separate process again, which won't share the context.
Ive used default django superuser to create an admin and created a DB using it. But now in that i want to add a python script to each data entry i.e. row. How do i do this???
Nothing special. Used basic Django.
There doesn't seem to be anything particularly complex here. You're just asking if you can use a field on the model to choose something; of course you can.
For instance:
# actions.py
def thing1(obj):
# do something with obj
def thing2(obj):
# do something else with obj
# models.py
from . import actions
ACTIONS = {
"do_thing_1": actions.thing1,
"do_thing_2": actions.thing2,
}
ACTION_CHOICES = [(action, action) for action in ACTIONS]
class MyModel(models.Model):
action = models.CharField(max_length=20, choices=ACTION_CHOICES)
def do_action(self):
return ACTIONS[self.action](self)
i am a beginner to Django i am trying to use permissions to permit access to specific view functions via a decorator, for specific user types only. Right now i am totaly confused by all kinds of stuff i have read about and seam not to figure out how i should do this.
I have two different kinds of users let it be UserTypeONE and UserTypeTWO.
UserTypeONE and UserTypeTWO should have access to specific views only.
Here is my code:
myuserTypes.py
class UserTypeONE(models.Model):
lieOtO_User = models.OneToOneField(settings.AUTH_USER_MODEL)
lie_SomeAttribute= models.CharField(max_length=300, help_text ='Name')
class Meta:
permissions = (('Can_View_MyShop', 'Can see Shop View'),)
class UserTypeTWO(models.Model):
lieOtO_User = models.OneToOneField(settings.AUTH_USER_MODEL)
lie_SomeOtherAttribute= models.CharField(max_length=300, help_text ='Name')
class Meta:
permissions = (('Can_View_Targets', 'Can see the Targets'),)
Here is what i am trying to do in my views.py
#login_required
#permission_required('UserTypeONE.Can_View_MyShop', raise_exception=True)
def MyShopView(request):
#do something
i also tried
#user_passes_test(lambda u: u.usertypeone.permission('Can_View_MyShop'))
As you guys can see i am an absolute beginner unfortunately all documentations and examples havent done me any good instead i am even more confused.
I would really appreciate help on this.
I would use user_passes_test() here since you specifically want to restrict specific views.
First, define a couple of functions that return True when you're dealing with a user who should be able to see your content. It looks like your UserTypeOne and UserTypeTwo models extend the base User model with a one-to-one relationship, so you can use hasattr to check if a given base user has one of those attributes:
def type_one_only(user):
if hasattr (user, 'usertypeone'):
return True
else:
return False
def type_two_only(user):
#same thing without if/else
return hasattr(user, 'usertypetwo')
Now when you have a view that you want to restrict to one user type, you can add a user_passes_test decorator before it:
#user_passes_test(type_one_only, login_url='/')
def my_view(request):
...
login_url is where a user will be sent if they do not pass the test you've indicated.
What is the difference between, say, a EmailField and a validator_email? And is it a bad idea to use both?
Or for those who perfer code
import django.db import models
email = models.EmailField()
vs
import django.db import models
email = models.CharField( max_length=75, validators = validate_email )
From the doc it seems like you could also use validators inside forms but if you already specify a validation restriction inside models.py, then you don't need specify again in the forms, right? So it seems better to me to take care of all of the restriction inside models.py.
I suppose the difference is very little, but then you would be violating the DRY principal, which you probably shouldn't do, unless you have a good reason to do it.
If you go to the code base:
#django.db.fields.__init__.py
class EmailField(CharField):
default_validators = [validators.validate_email]
description = _("E-mail address")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = kwargs.get('max_length', 75)
CharField.__init__(self, *args, **kwargs)
def formfield(self, **kwargs):
# As with CharField, this will cause email validation to be performed
# twice.
defaults = {
'form_class': forms.EmailField,
}
defaults.update(kwargs)
return super(EmailField, self).formfield(**defaults)
As you can see, the model inherits from Charfield, so you lose nothing by using emailfield, where appropriate. Furthermore, the default validator is validate_email. Additionally you get the description variable already defined for you. Lastly, on the backend it is already setting max_length for you at '75'. You could of course override this easily enough by defining a max_length in the same way you would when creating a CharField.
You can see formfields() is returning forms.EmailField from django.forms.
Looking at that, you can see:
#django.forms.fields.py
class EmailField(CharField):
default_error_messages = {
'invalid': _(u'Enter a valid e-mail address.'),
}
default_validators = [validators.validate_email]
def clean(self, value):
value = self.to_python(value).strip()
return super(EmailField, self).clean(value)
However, you would lose any default values that using the EmailField might provide, such as the "correct" error message and the custom clean() method.
In the end, while it looks small, actually a good bit of work has already been done for you. So, in general, you shouldn't violate the DRY principal unless you have a good reason to do so.
Edit:
Regarding the second question, you want the form to validate against whatever criteria you are concerned about, so when you call form.is_valid() it returns True / False when it should and generates the appropriate failure message. Otherwise, is_valid() would validate True, and when you model goes to save, it would fail silently, which would be very hard to track down.
Whenever I'm editing object A with a foreign key to object B, a plus option "add another" is available next to the choices of object B. How do I remove that option?
I configured a user without rights to add object B. The plus sign is still available, but when I click on it, it says "Permission denied". It's ugly.
I'm using Django 1.0.2
The following answer was my original answer but it is wrong and does not answer OP's question:
Simpler solution, no CSS hack and no editing Django codebase:
Add this to your Inline class:
max_num=0
(this is only applicable to inline forms, not foreign key fields as OP asked)
The above answer is only useful to hide the "add related" button for inline forms, and not foreign keys as requested.
When I wrote the answer, IIRC the accepted answer hid both, which is why I got confused.
The following seems to provide a solution (though hiding using CSS seems the most feasible thing to do, especially if the "add another" buttons of FKs are in inline forms):
Django 1.7 removing Add button from inline form
Though most of the solutions mentioned here work, there is another cleaner way of doing it. Probably it was introduced in a later version of Django, after the other solutions were presented. (I'm presently using Django 1.7)
To remove the "Add another" option,
class ... #(Your inline class)
def has_add_permission(self, request):
return False
Similarly if you want to disable "Delete?" option, add the following method in Inline class.
def has_delete_permission(self, request, obj=None):
return False
N.B. Works for DJango 1.5.2 and possibly older. The can_add_related property appeared around 2 years ago.
The best way I've found is to override your ModelAdmin's get_form function. In my case I wanted to force the author of a post to be the currently logged in user. Code below with copious comments. The really important bit is the setting of widget.can_add_related:
def get_form(self,request, obj=None, **kwargs):
# get base form object
form = super(BlogPostAdmin,self).get_form(request, obj, **kwargs)
# get the foreign key field I want to restrict
author = form.base_fields["author"]
# remove the green + by setting can_add_related to False on the widget
author.widget.can_add_related = False
# restrict queryset for field to just the current user
author.queryset = User.objects.filter(pk=request.user.pk)
# set the initial value of the field to current user. Redundant as there will
# only be one option anyway.
author.initial = request.user.pk
# set the field's empty_label to None to remove the "------" null
# field from the select.
author.empty_label = None
# return our now modified form.
return form
The interesting part of making the changes here in get_form is that author.widget is an instance of django.contrib.admin.widgets.RelatedFieldWidgetWrapper where as if you try and make changes in one of the formfield_for_xxxxx functions, the widget is an instance of the actual form widget, in this typical ForeignKey case it's a django.forms.widgets.Select.
I use the following approaches for Form and InlineForm
Django 2.0, Python 3+
Form
class MyModelAdmin(admin.ModelAdmin):
#...
def get_form(self,request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
user = form.base_fields["user"]
user.widget.can_add_related = False
user.widget.can_delete_related = False
user.widget.can_change_related = False
return form
Inline Form
class MyModelInline(admin.TabularInline):
#...
def get_formset(self, request, obj=None, **kwargs):
formset = super().get_formset(request, obj, **kwargs)
user = formset.form.base_fields['user']
user.widget.can_add_related = False
user.widget.can_delete_related = False
user.widget.can_change_related = False
return formset
The answer by #Slipstream shows how to implement the solution, viz. by overriding the attributes for the formfield's widget, but, in my opinion, get_form is not the most logical place to do this.
The answer by #cethegeek shows where to implement the solution, viz. in an extension of formfield_for_dbfield, but does not provide an explicit example.
Why use formfield_for_dbfield? Its docstring suggests that it is the designated hook for messing with form fields:
Hook for specifying the form Field instance for a given database Field instance.
It also allows for (slightly) cleaner and clearer code, and, as a bonus, we can easily set additional form Field attributes, such as initial value and/or disabled (example here), by adding them to the kwargs (before calling super).
So, combining the two answers (assuming the OP's models are ModelA and ModelB, and the ForeignKey model field is named b):
class ModelAAdmin(admin.ModelAdmin):
def formfield_for_dbfield(self, db_field, request, **kwargs):
# optionally set Field attributes here, by adding them to kwargs
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
if db_field.name == 'b':
formfield.widget.can_add_related = False
formfield.widget.can_change_related = False
formfield.widget.can_delete_related = False
return formfield
# Don't forget to register...
admin.site.register(ModelA, ModelAAdmin)
NOTE: If the ForeignKey model field has on_delete=models.CASCADE, the can_delete_related attribute is False by default, as can be seen in the source for RelatedFieldWidgetWrapper.
Look at django.contrib.admin.options.py and check out the BaseModelAdmin class, formfield_for_dbfield method.
You will see this:
# For non-raw_id fields, wrap the widget with a wrapper that adds
# extra HTML -- the "add other" interface -- to the end of the
# rendered output. formfield can be None if it came from a
# OneToOneField with parent_link=True or a M2M intermediary.
if formfield and db_field.name not in self.raw_id_fields:
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site)
I think your best bet is create subclass of ModelAdmin (which in turn is a subclass of BaseModelAdmin), base your model on that new class, override formfield_fo_dbfield and make it so that it won't/or will conditionally wrap the widget in RelatedFieldWidgetWrapper.
One could argue that if you have a user that doesn't have rights to adding related objects, the RelatedFieldWidgetWrapper should not display the add link? Maybe this is something that is deserving of mention in Django trac?
DEPRECATED ANSWER
Django has since made this possible.
Have you considered instead, using CSS to simply not show the button? Maybe that's a little too hacky.
This is untested, but I'm thinking...
no-addanother-button.css
#_addanother { display: none }
admin.py
class YourAdmin(admin.ModelAdmin):
# ...
class Media:
# edit this path to wherever
css = { 'all' : ('css/no-addanother-button.css',) }
Django Doc for doing this -- Media as a static definition
Note/Edit: The documentation says the files will be prepended with the MEDIA_URL but in my experimentation it isn't. Your mileage may vary.
If you find this is the case for you, there's a quick fix for this...
class YourAdmin(admin.ModelAdmin):
# ...
class Media:
from django.conf import settings
media_url = getattr(settings, 'MEDIA_URL', '/media/')
# edit this path to wherever
css = { 'all' : (media_url+'css/no-addanother-button.css',) }
I'm using Django 2.x and I think I found best solution, at least for my case.
The HTML file to the "Save and Add Another" button is on your_python_installation\Lib\site-packages\django\contrib\admin\templates\admin\subtmit_line.html.
Copy that html file and paste to your project like so your_project\templates\admin\submit_line.html.
Open it and comment/delete the button code as desired:
{#{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}#}
I know this problem is already answered. But maybe someone in the future have a similar case with me.
Based on cethegeek answer I made this:
class SomeAdmin(admin.ModelAdmin):
form = SomeForm
def formfield_for_dbfield(self, db_field, **kwargs):
formfield = super(SomeAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'some_m2m_field':
request = kwargs.pop("request", None)
formfield = self.formfield_for_manytomany(db_field, request, **kwargs) # for foreignkey: .formfield_for_foreignkey
wrapper_kwargs = {'can_add_related': False, 'can_change_related': False, 'can_delete_related': False}
formfield.widget = admin.widgets.RelatedFieldWidgetWrapper(
formfield.widget, db_field.remote_field, self.admin_site, **wrapper_kwargs
)
return formfield
The way i fixed a similar situation based on django docs
https://docs.djangoproject.com/en/3.2/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.extra
The outcome of the solution is that it lets you add an inline just for that instance. Or in different words: add an inline and just one; no other buttons.
#models.py
class Model_A(models.Model):
...
class Model_B(models.Model):
...
relevant_field = models.ForeignKey(Model_A, related_name='Model_B_relevant_field')
# forms.py or someotherfile.py
from django.contrib.admin import StackedInline, TabularInline
class Model_B_Inline(StackedInline):
verbose_name = 'Some Name'
...
def get_extra(self, request, obj=None, *args, **kwargs):
the_extra = super().get_extra(request, obj=obj, *args, **kwargs)
self.extra = 1
if obj:
the_counter = obj.Model_B_relevant_field.count()
else:
the_counter = -1
self.max_num = the_counter + 1
return the_extra
As it's been pointed out in comments:
max_num = 0
It has also been confirmed here:
https://code.djangoproject.com/ticket/13424#comment:1
PS: This also works for inlines.
django.contrib.admin.widgets.py
(Django Install Dir)/django/contrib/admin/widgets.py: Comment everything between Line 239 & Line 244:
if rel_to in self.admin_site._registry: # If the related object has an admin interface:
# TODO: "id_" is hard-coded here. This should instead use the correct
# API to determine the ID dynamically.
output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
(related_url, name))
output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another')))