I am creating a Django Project(which has a lot of Django apps in it) and I need some help.
I only need to add a small feature.
The admin will decide a date upto which the app will be shown to all.
I want the app to automatically redirect all users to a specific page after the mentioned date.
How should I proceed ?
A simple middleware (https://docs.djangoproject.com/en/1.7/topics/http/middleware/) will resolve your requirement. Something like this:
import datetime
from django.conf import settings
from django.http import HttpResponseRedirect
class OnOffMiddleware(object,):
def process_request(request):
if datetime.datetime.now() > settings.SHOW_DATE:
# Should redirect people
return HttpResponseRedirect('redirect to a specifc page')
else:
# Continue as usual
return None
Put the above in a module somewhere in your project and add it to the top of your MIDDLEWARE_CLASSES setting (https://docs.djangoproject.com/en/1.7/topics/http/middleware/#activating-middleware).
Related
I have a website with multiple languages. However, these languages should NEVER be chosen by the visitor and instead are either a) decided based on the domain name (same site runs under different domains, one for each language), or b) retrieved from the database, depending on the exact page opened.
From the manual I understand I should be using the django.utils.translation.activate() function to explicityly set the language. However, I am not having any luck.
If I am correct, this is the code I need (no need for me to set cookies, so this is what I isolated):
from django.utils import translation
translation.activate("fr")
Is this right? Where exactly should I place this? Can I use the sites framework to link a language to a particular language? Here is what I have tried in my views.py file, but it isn't working. No error - just no translation. Is there a way I can debug this to figure out where it goes wrong?
# views.py
from .models import *
from django.shortcuts import render
from django.utils import translation
from django.conf import settings
# I am trying to activate a default setting for the site, based on the domain of the site
if settings.SITE_ID == 1:
translation.activate("fr")
else:
translation.activate("en")
def homepage(request)
return render(request, "index.html")
def listing(request, id)
listing = Listing.objects.get(pk=id)
language = listing.language
translation.activate(language) # For particular pages, I need a different language, based on a value from the database. This is what I tried.
return render(request, "index.html")
According to your use case, the best place to use translation.activate() is in some middleware, since the language is site/domain dependant.
When you are doing:
# views.py
from .models import *
from django.shortcuts import render
from django.utils import translation
from django.conf import settings
# I am trying to activate a default setting for the site, based on the domain of the site
if settings.SITE_ID == 1:
translation.activate("fr")
else:
translation.activate("en")
The call is made when the module is parsed, not when a HTTP request is made and processed.
With a middleware, the proper locale can be selected like this:
# middlewares.py
def site_locale_middleware(get_response):
def middleware(request):
if request.site.id == 1:
translation.activate("fr")
elif request.site.id == 2:
translation.activate("es")
else:
translation.activate("en")
response = get_response(request)
return response
return middleware
And add this in your settings:
MIDDLEWARES = [
"django.contrib.sites.middleware.CurrentSiteMiddleware""",
# your middleware should be placed after CurrentSiteMiddleware
# so that request.site is available
"yourproject.middlewares.site_locale_middleware",
]
This will ensure you have a proper language setup for each site, and you can override it when needed with:
def listing(request, id)
listing = Listing.objects.get(pk=id)
language = listing.language
with translation.override(language):
# using override here is important, because it means it will only override
# the language when the response is rendered, and revert back to the previous language afterwards
return render(request, "index.html")
I have a Django 2.2 based project that uses a custom user model, the built-in Auth app and Django-All-Auth for user management. Almost every page on the site is behind a login and we use varying levels of permissions to determine what can be accessed a user.
So far, so good, but now I'm being asked to designate everything behind a specific part of the site as "sensitive", requiring a second login prompt using the same login credentials. What this means is that the client wants to see a login appear when they try to access anything under /top-secret/ the first time in a set time, say 30 mins, regardless of whether they're already logged in or not.
I've dug around on the internet for ideas on how to do this, but so far I've been unable to find a good solution. Has anyone here had any experience with something similar, and if so, could they point me in the right direction?
Figured out how to make this work in my situation. I debated deleting this question, but on the off chance that there's someone else out there that wants to do something similar, here's how I did it.
Create a new middleware with the following:
from allauth.account.adapter import get_adapter
from datetime import datetime
from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
class ExtraLoginPromptMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
for url in settings.EXTRA_LOGIN_URLS:
if request.path.startswith(url):
last_seen = request.session.get(settings.EXTRA_LOGIN_SESSION_VARIABLE)
if not last_seen:
last_seen = request.user.last_login
else:
last_seen = datetime.fromisoformat(last_seen)
if last_seen + relativedelta(seconds=settings.EXTRA_LOGIN_EXPIRY) < timezone.now():
# NB, this uses allauth, if you want to use django.auth, just
# call django.contrib.auth's logout instead
get_adapter(request).logout(request)
return redirect('{}?next={}'.format(reverse('account_login'), url))
else:
request.session[settings.EXTRA_LOGIN_SESSION_VARIABLE] = timezone.now().isoformat()
return self.get_response(request)
To make use of this, you'll want to add this middleware after SessionMiddleware and AuthenticationMiddleware
You'll also want to define a few variables in your settings:
EXTRA_LOGIN_URLS = ['/top-secret/', 'another-secret']
EXTRA_LOGIN_EXPIRY = 120 # How long before the "extra security" session expires
EXTRA_LOGIN_SESSION_VARIABLE = 'extra-login-last-seen'
I am using django-two-factor-auth for a webapp. I cannot access the admin page.
I know I am entering the correct credentials. When I input incorrect credentials, I get an appropriate error message.
When I input the correct credentials, the page simply reloads with this URL:
http://localhost:8080/account/login/?next=/inveskore/
These are my settings related to two_factor:
LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = '/inveskore'
TWO_FACTOR_SMS_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
This is the associated URL path:
path('admin/', admin.site.urls),
According to this, it results from the admin user not having 2FA set.
So, how do you set 2FA for the admin user if you can't access the site?
EDIT:
I took down the 2FA login requirements for the site and then added a phone device. No luck.
I recently ran into this scenario and created this solution based on a comment there:
https://github.com/Bouke/django-two-factor-auth/issues/219#issuecomment-494382380
I subclassed AdminSiteOTPRequired and then specified it as the admin class to use
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.views import redirect_to_login
from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url
from django.urls import reverse
from django.utils.http import is_safe_url
from two_factor.admin import AdminSiteOTPRequired, AdminSiteOTPRequiredMixin
class AdminSiteOTPRequiredMixinRedirSetup(AdminSiteOTPRequired):
def login(self, request, extra_context=None):
redirect_to = request.POST.get(
REDIRECT_FIELD_NAME, request.GET.get(REDIRECT_FIELD_NAME)
)
# For users not yet verified the AdminSiteOTPRequired.has_permission
# will fail. So use the standard admin has_permission check:
# (is_active and is_staff) and then check for verification.
# Go to index if they pass, otherwise make them setup OTP device.
if request.method == "GET" and super(
AdminSiteOTPRequiredMixin, self
).has_permission(request):
# Already logged-in and verified by OTP
if request.user.is_verified():
# User has permission
index_path = reverse("admin:index", current_app=self.name)
else:
# User has permission but no OTP set:
index_path = reverse("two_factor:setup", current_app=self.name)
return HttpResponseRedirect(index_path)
if not redirect_to or not is_safe_url(
url=redirect_to, allowed_hosts=[request.get_host()]
):
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
return redirect_to_login(redirect_to)
Then in urls.py:
from django.contrib import admin
admin.site.__class__ = AdminSiteOTPRequiredMixinRedirSetup
I cannot comment yet but the answer above by #saschwarz worked like a charm with Django 2.2 and django-two-factor-auth. The only thing missing from his code is the additional import:
from django.http import HttpResponseRedirect
After that I was able to use this line:
admin.site.__class__ = AdminSiteOTPRequiredMixinRedirSetup
to quickly enforce two factor for admin users.
This is really missing from the docs for django-two-factor-auth... I looked at the documentation for quite a while and couldn't find a clear way to generate a qr code. It's only really demonstrated in the demo app, and not in a very modular way.
The solution from saschewarz didn't solve the problem in my app.
So I found another one where standard admin login is shown.
django-two-factor-auth by default patch your admin urls with function 'patch_admin' from two_factor\admin.py so you always will see your standard site login for admin login.
To solve this you can comment out 2 functions in two_factor\admin.py
def patch_admin()
and
def unpatch_admin()
And comment out in two_factor\apps.py
def ready(self)
And to use two factor authentication in admin site add this code in main urls.py (where is path to admin):
from django_otp.admin import OTPAdminSite
admin.site.__class__ = OTPAdminSite
I implemented authentication management using Django auth with the default admin site but then I wanted to use my own AdminSite to rewrite some behaviors:
class OptiAdmin(admin.AdminSite):
site_title = "Optimizer site's admin"
#...Other stuff here
Then registered my own models:
admin_site = OptiAdmin(name='opti_admin')
admin.site.register(MyModel, MyModelAdmin)
#Other stuff here
But when I go to the admin site I am only able to see the models I just registered, which sounds fair to me but I would like to see all the other apps models in this new custom site including the auth's users and groups and I don't know how to do this automatically like the default admin does, pls help :).
Create your own AdminSite with a simple __init__() override.
Import your admin in urls.py.
Replacing the Django Admin and getting the autodiscover() behavior is possible with minimal effort. Here's a project structure generated in the typical django-admin startproject project fashion:
project/
manage.py
project/
__init__.py
settings.py
urls.py
wsgi.py
admin.py # CREATE THIS FILE
project/admin.py: (I think it makes the most sense to do this at the project level.)
from django.contrib.admin import * # PART 1
class MyAdminSite(AdminSite):
site_header = "My Site"
def __init__(self, *args, **kwargs):
super(MyAdminSite, self).__init__(*args, **kwargs)
self._registry.update(site._registry) # PART 2
site = MyAdminSite()
project/urls.py (snippet):
from . import admin # PART 3
urlpatterns = [
url(r'^admin/', admin.site.urls),
]
Part 1 is simple Python. By importing everything from django.contrib.admin into your namespace, it acts as a drop-in replacement. I suppose you don't have to do this, but it helps preserve expectations. Part 3, simply connect up your admin. Part 2 is the real trick. As the documentation says, autodiscover() is called to do the work. All autodiscover does is go through INSTALLED_APPS attempting to import a file called admin.py. Importing runs the code of course and that code is doing the same thing you do to register models (example by decorator and example by method). No magic. You don't have to register your models with your customized admin (as the documentation says).
Autodiscover looks smarter than it is with its register_to kwarg. That indicates you could call autodiscover() yourself passing your own admin. Nope; there's no wiring connected there (future feature?). The assignment happens here and is fixed to the native AdminSite instance here (or here using the decorator). Django contrib models register to that instance and so will any third-party libraries. It's not something you can hook into.
Here's the trick though, _registry is just a dictionary mapping. Let Django autodiscover all the things and then just copy the mapping. That's why self._registry.update(site._registry) works. "self" is your customized AdminSite instance, "site" is Django's instance and you can register your models with either.
(Final note: If models are missing, it's because of import order. All the registration to Django's AdminSite needs to happen before you copy _registry. Registering directly to your customized admin is probably the easiest thing.)
The Django docs suggest using SimpleAdminConfig with a custom admin site.
INSTALLED_APPS = (
...
'django.contrib.admin.apps.SimpleAdminConfig',
...
)
That prevents the models being registered with the default AdminSite.
The docs seem to assume that you will import the models individually and add them to your custom admin site:
from django.contrib.auth.models import Group, User
from django.contrib.auth.admin import GroupAdmin, UserAdmin
admin_site.register(Group, GroupAdmin)
admin_site.register(User, UserAdmin)
This would be very repetitive if you have models in many apps. It doesn't offer any advice how to automatically register models from all your apps with your custom site.
You could try monkey patching admin, and replacing admin.site with your own.
from django.contrib import admin
admin.site = OptiAdmin(name='opti_admin')
Then, when code called admin.site.register(), it would register the model with your admin site. This code would have to run before any models were registered. You could try putting it in the AppConfig for your app, and make sure that your app is above django.contrib.admin.
Adding to JCotton's great answer:
Using django 2.0, overriding site_header and site_title in the custom admin site only works for the index page.
To get it to work with all admin views, extend JCotton's code with the following:
def __init__(self, *args, **kwargs):
super(MyAdminSite, self).__init__(*args, **kwargs)
self._registry.update(site._registry) # PART 2
for model, model_admin in self._registry.items():
model_admin.admin_site = self
Just include init method in your CustomAdminSite class like this.
class CustomAdminSite(admin.AdminSite):
def __init__(self, *args, **kwargs):
super(CustomAdminSite, self).__init__(*args, **kwargs)
self._registry.update(admin.site._registry)
In the docs for Django's System check framework it says:
Checks should be registered in a file that’s loaded when your application is loaded; for example, in the AppConfig.ready() method.
None of the examples on that page, or around the AppConfig.ready() method, show how to do this. Given a checking method like:
from django.core.checks import register, Tags
#register(Tags.compatibility)
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
return errors
How would you do this in/from the AppConfig.ready() method? Is one called from the other? Which file should the above method go in? Do you add #register(...) to the ready() method?
From reading the examples on this page about the Apps Registry and System Checks Framework, it seems there are (at least) two ways to add your own System Checks. To adapt that page's examples (assuming you're creating an app called myapp):
1) Create a myapp/checks.py file like this:
from django.apps import apps as camelot_apps
from django.core.checks import register, Warning
from django.core.checks import Tags as DjangoTags
class Tags(DjangoTags):
"""Do this if none of the existing tags work for you:
https://docs.djangoproject.com/en/1.8/ref/checks/#builtin-tags
"""
my_new_tag = 'my_new_tag'
#register(Tags.my_new_tag)
def check_taggit_is_installed(app_configs=None, **kwargs):
"Check that django-taggit is installed when usying myapp."
errors = []
try:
from taggit import models
except ImportError:
errors.append(
Warning(
"The django-taggit app is required to use myapp.",
hint=("Install django-taggit"),
# A unique ID so it's easier to find this warning:
id='myapp.W001',
)
)
return errors
And then in myapp/__init__.py (create it if it doesn't exist):
from . import checks
Running this should run that check above:
$ ./manage.py check myapp
2) Or, as I thought in my initial question, you can register that check in your AppConfig. So, keep the above code in myapp/check.py, but remove the #register(Tags.my_new_tag) line.
Then create myapp/apps.py containing this:
from django.core.checks import register
from .checks import Tags, check_taggit_is_installed
class MyappConfig(AppConfig):
name = 'myapp'
def ready(self):
super(MyappConfig, self).ready()
register(Tags.my_new_tag)(check_taggit_is_installed)
And alter myapps/__init__.py so it contains this:
from . import checks
default_app_config = 'myapp.apps.MyappConfig'
The first example seems simpler, with no need for a custom AppConfig.
Django recommends:
Checks should be registered in a file that’s loaded when your application is loaded; for example, in the AppConfig.ready() method.
Therefore, place the checks code in a checks.py file. Then simply in apps.py, as with signals:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'MyApp'
verbose_name = "My App"
def ready(self):
# import myapp.signals
import myapp.checks
My guess is that you can just place this code in ready() method. For example of usage you can take a look at django.contrib.auth (or other contrib app). This app has check in checks.py file, that imported in apps.py and registered in ready() method: checks.py, apps.py.