Commit 167cdaad authored by fred.perso's avatar fred.perso

add geodata and improve User model and user forms

parent 072848be
/static
/sights/migrations
**/migrations
/pyenv
/.idea
.git
......@@ -8,5 +8,4 @@
*.pyc
/media
secretkey.txt
initial_data/shapefiles/*
......@@ -2,11 +2,11 @@ from django.contrib import admin
from leaflet.admin import LeafletGeoAdminMixin, LeafletGeoAdmin
from django.contrib.auth.admin import UserAdmin
from .models import User
from .models import Profile
class GeoUserAdmin(LeafletGeoAdmin):
list_display = ['id','username','email','first_name','last_name','is_superuser','is_staff','is_active','last_login']
search_fields = ['username','email','first_name','last_name']
list_filter = ('is_active','is_staff','is_superuser')
admin.site.register(User, GeoUserAdmin)
admin.site.register(Profile, GeoUserAdmin)
import django_filters
from dal import autocomplete
from .models import User
from .models import Profile
# municiplist_url = 'municipality_autocomplete'
......@@ -13,7 +13,7 @@ class UserFilter(django_filters.FilterSet):
last_name = django_filters.CharFilter(label="Nom de famille", lookup_expr='icontains')
class Meta:
model = User
model = Profile
fields = [
'username', 'first_name', 'last_name', 'addr_city_code'
]
......
from crispy_forms.helper import FormHelper
from crispy_forms_foundation.layout import (AccordionHolder, AccordionItem, Fieldset,
from crispy_forms_foundation.layout import (AccordionHolder, AccordionItem, Fieldset, Field,
ButtonHolder, Column, Layout, Row, Submit, InlineSwitchField)
from dal import autocomplete
from django.contrib.auth.forms import UserCreationForm, PasswordChangeForm, PasswordResetForm
from django.forms import CharField
from django.forms.widgets import HiddenInput
from django.utils.translation import ugettext_lazy as _
from leaflet.forms.widgets import LeafletWidget
from django.forms.widgets import HiddenInput
from django.forms import CharField
from .models import User
from .models import Profile
class UserCreateForm(UserCreationForm):
username = CharField(initial='tmpusername123')
class Meta:
model = User
model = Profile
fields = UserCreationForm.Meta.fields + (
'first_name', 'last_name', 'email', 'is_resp', 'resp_territory', 'geom', 'access_all_data',
'edit_all_data', 'catchauth',
# 'organism','mobile_phone', 'home_phone', 'addr_appt', 'addr_building', 'addr_street', 'addr_city', 'addr_city_code',
# 'addr_dept', 'addr_country',
'comment', 'id_bdsource', 'bdsource', 'geom')
widgets = {'username':HiddenInput(), 'geom': LeafletWidget(),
widgets = {'username': HiddenInput(), 'geom': LeafletWidget(),
'resp_territory': autocomplete.ModelSelect2Multiple(url='territory_autocomplete'), }
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.form_method = 'post'
......@@ -36,14 +35,6 @@ class UserCreateForm(UserCreationForm):
AccordionHolder(
AccordionItem('Informations principales',
Fieldset(_("Caractéristiques principales"),
Row(
# Column(
# 'username', css_class='small-12 medium-12 large-4'),
Column('password1',
css_class='small-12 medium-6 large-6'),
Column('password2',
css_class='small-12 medium-6 large-6'),
),
Row(
Column('email',
css_class='small-12 medium-12 large-4'),
......@@ -52,22 +43,35 @@ class UserCreateForm(UserCreationForm):
Column('last_name',
css_class='small-12 medium-6 large-4'),
),
Row(
Field('username', type="hidden"),
Column('password1',
css_class='small-12 medium-6 large-6'),
Column('password2',
css_class='small-12 medium-6 large-6'),
),
),
Fieldset(_("Responsabilité et autorisations"),
Column(
InlineSwitchField('is_resp', label_column='large-12',
input_column='large-12'),
css_class='small-12 medium-3 large-2'
Row(
Column(
InlineSwitchField('is_resp', label_column='large-12',
input_column='large-12'),
css_class='small-12 medium-3 large-2'
),
Column(
'resp_territory', css_class='small-12 medium-9 large-10'),
),
Row(
Column(InlineSwitchField('access_all_data', label_column='large-12',
input_column='large-12'),
css_class='small-12 large-6'),
Column(InlineSwitchField('edit_all_data', label_column='large-12',
input_column='large-12'),
css_class='small-12 large-6'),
Column('catchauth', css_class='large-12'),
),
Column(
'resp_territory', css_class='small-12 medium-9 large-10'),
Column(InlineSwitchField('access_all_data', label_column='large-12',
input_column='large-12'),
css_class='small-12 large-6'),
Column(InlineSwitchField('edit_all_data', label_column='large-12',
input_column='large-12'),
css_class='small-12 large-6'),
Column('catchauth', css_class='large-12'),
),
# Fieldset("Coordonnées",
#
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-18 16:13
from __future__ import unicode_literals
import django.contrib.auth.models
import django.contrib.auth.validators
import django.contrib.gis.db.models.fields
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('is_resp', models.BooleanField(default=False, verbose_name='Responsable dun territoire')),
('access_all_data', models.BooleanField(default=False, verbose_name='Peut voir toutes les données')),
('edit_all_data', models.BooleanField(default=False, verbose_name='Peut éditer toutes les données')),
('organism', models.CharField(blank=True, max_length=255, null=True, verbose_name='Organisme')),
('home_phone', models.CharField(blank=True, max_length=50, null=True, verbose_name='Numéro de téléphone fixe')),
('mobile_phone', models.CharField(blank=True, max_length=50, null=True, verbose_name='Numéro de téléphone portable')),
('addr_appt', models.CharField(blank=True, max_length=50, null=True, verbose_name='Appartement')),
('addr_building', models.CharField(blank=True, max_length=255, null=True, verbose_name='Immeuble')),
('addr_street', models.CharField(blank=True, max_length=255, null=True, verbose_name='Rue')),
('addr_city', models.CharField(blank=True, max_length=255, null=True, verbose_name='Commune')),
('addr_city_code', models.CharField(blank=True, max_length=10, null=True, verbose_name='Code postal')),
('addr_dept', models.CharField(blank=True, max_length=255, null=True, verbose_name='Département')),
('addr_country', models.CharField(blank=True, max_length=255, null=True, verbose_name='Pays')),
('comment', models.TextField(blank=True, null=True, verbose_name='Commentaire')),
('id_bdsource', models.CharField(blank=True, max_length=100, null=True)),
('bdsource', models.CharField(blank=True, max_length=100, null=True)),
('geom', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Localisation géographique')),
('timestamp_create', models.DateTimeField(auto_now_add=True)),
('timestamp_update', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name_plural': 'Détails des utilisateurs',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-18 16:13
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('accounts', '0001_initial'),
('auth', '0008_alter_user_username_max_length'),
('geodata', '0001_initial'),
('management', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='catchauth',
field=models.ManyToManyField(blank=True, to='management.CatchAuth', verbose_name='Authorisations de capture'),
),
migrations.AddField(
model_name='user',
name='coord_territory',
field=models.ManyToManyField(blank=True, to='geodata.Territory', verbose_name='Territoires du rôle de coordinateur'),
),
migrations.AddField(
model_name='user',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
),
migrations.AddField(
model_name='user',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-19 00:08
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_auto_20171118_1713'),
]
operations = [
migrations.AddField(
model_name='user',
name='creator',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-20 23:54
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_user_creator'),
]
operations = [
migrations.RenameField(
model_name='user',
old_name='coord_territory',
new_name='resp_territory',
),
]
import re
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import AbstractUser, AbstractBaseUser
from django.contrib.auth.models import UserManager, PermissionsMixin
from django.contrib.auth.validators import ASCIIUsernameValidator, UnicodeUsernameValidator
from django.contrib.gis.db import models
from django.template.defaultfilters import slugify
from django.urls import reverse_lazy
from django.utils import six, timezone
from django.utils.translation import ugettext_lazy as _
from django.template.defaultfilters import slugify
from django.contrib.auth import get_user_model
from geodata.models import Territory
from management.models import CatchAuth
......@@ -16,27 +21,63 @@ def generate_username(first_name, last_name):
fnl = first_name.split('-')
fni = ""
for name in fnl:
fni=fni+name[0]
fni = fni + name[0]
val = "{0}{1}".format(fni, last_name).lower()
val = re.sub('[!\' -@#$%,]','',val)
val = re.sub('[!\' -@#$%,]', '', val)
val = slugify(val)
# val =
x = 0
while True:
if x == 0 and User.objects.filter(username=val).count() == 0:
if x == 0 and get_user_model().objects.filter(username=val).count() == 0:
return val
else:
x += 1
new_val = "{0}{1}".format(val, x)
if User.objects.filter(username=new_val).count() == 0:
if get_user_model().objects.filter(username=new_val).count() == 0:
return new_val
if x > 1000000:
raise Exception("Name is super popular!")
class User(AbstractUser):
class Profile(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator() if six.PY3 else ASCIIUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
first_name = models.CharField(_('first name'), max_length=30)
last_name = models.CharField(_('last name'), max_length=30)
email = models.EmailField(_('email address'))
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
is_resp = models.BooleanField(default=False, verbose_name=_('Responsable d''un territoire'))
resp_territory = models.ManyToManyField(Territory, blank=True,
verbose_name=_('Territoires du rôle de coordinateur'))
verbose_name=_('Territoires du rôle de coordinateur'))
access_all_data = models.BooleanField(default=False, verbose_name=_('Peut voir toutes les données'))
edit_all_data = models.BooleanField(default=False, verbose_name=_('Peut éditer toutes les données'))
catchauth = models.ManyToManyField(
......@@ -65,17 +106,96 @@ class User(AbstractUser):
creator = models.ForeignKey(
settings.AUTH_USER_MODEL, null=True, db_index=True, editable=False)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email', 'first_name','last_name']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def save(self, *args, **kwargs):
self.username = generate_username(self.first_name,self.last_name)
# self.creator = current_user()
super(User, self).save(*args, **kwargs)
self.username = generate_username(self.first_name, self.last_name)
self.last_name = self.last_name.upper()
super(Profile, self).save(*args, **kwargs)
def clean(self):
super(Profile, self).clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Returns the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"Returns the short name for the user."
return self.first_name
def __str__(self):
return self.username
def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email], **kwargs)
def get_absolute_url(self):
# return reverse('user_detail', kwargs={'pk': self.id_user})
return reverse_lazy('home')
class Meta:
verbose_name_plural = "Détails des utilisateurs"
#
#
# class User(AbstractUser):
# is_resp = models.BooleanField(default=False, verbose_name=_('Responsable d''un territoire'))
# resp_territory = models.ManyToManyField(Territory, blank=True,
# verbose_name=_('Territoires du rôle de coordinateur'))
# access_all_data = models.BooleanField(default=False, verbose_name=_('Peut voir toutes les données'))
# edit_all_data = models.BooleanField(default=False, verbose_name=_('Peut éditer toutes les données'))
# catchauth = models.ManyToManyField(
# CatchAuth,
# blank=True, verbose_name=_('Authorisations de capture'))
# organism = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Organisme'))
# home_phone = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('Numéro de téléphone fixe'))
# mobile_phone = models.CharField(max_length=50, blank=True, null=True,
# verbose_name=_('Numéro de téléphone portable'))
# addr_appt = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('Appartement'))
# addr_building = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Immeuble'))
# addr_street = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Rue'))
# addr_city = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Commune'))
# addr_city_code = models.CharField(max_length=10, blank=True, null=True, verbose_name=_('Code postal'))
# addr_dept = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Département'))
# addr_country = models.CharField(max_length=255, blank=True, null=True, verbose_name=_('Pays'))
# comment = models.TextField(blank=True, null=True, verbose_name=_('Commentaire'))
# id_bdsource = models.CharField(max_length=100, blank=True, null=True)
# bdsource = models.CharField(max_length=100, blank=True, null=True)
# geom = models.PointField(srid=settings.GEODATA_SRID, blank=True, null=True,
# verbose_name=_('Localisation géographique'))
# timestamp_create = models.DateTimeField(
# auto_now_add=True, editable=False)
# timestamp_update = models.DateTimeField(
# auto_now=True, editable=False)
# creator = models.ForeignKey(
# settings.AUTH_USER_MODEL, null=True, db_index=True, editable=False)
#
# REQUIRED_FIELDS = ['email', 'first_name', 'last_name']
#
# def save(self, *args, **kwargs):
# self.username = generate_username(self.first_name, self.last_name)
# # self.creator = current_user()
# super(User, self).save(*args, **kwargs)
#
# def __str__(self):
# return self.username
#
# def get_absolute_url(self):
# # return reverse('user_detail', kwargs={'pk': self.id_user})
# return reverse_lazy('home')
#
# class Meta:
# verbose_name_plural = "Détails des utilisateurs"
import django_tables2 as tables
from .models import User
from .models import Profile
class UserTable(tables.Table):
ACTIONS = """
<a href="{% url 'user_detail' pk=record.pk %}" title="Voir les informations"><i class="fi-eye"></i></a>
<a href="" title="Modifier"><i class="fi-page-edit"></i></a>
<a href="" title="Supprimer"><i class="fi-trash"></i></a>
"""
......@@ -16,7 +17,7 @@ class UserTable(tables.Table):
id = tables.Column(accessor='id', verbose_name='id')
class Meta:
model = User
model = Profile
attrs = {'class': 'responsive'}
fields = (
'actions', 'id', 'username', 'email', 'first_name', 'last_name', 'organism', 'is_resp', 'access_all_data',
......
......@@ -5,5 +5,6 @@ urlpatterns = [
url(r'^create/$', UserCreate.as_view(), name='user_create'),
url(r'^password/$', change_password, name='change_password'),
url(r'^list/$', UserListView.as_view(), name='user_search'),
url(r'^profile/$', AccountDetail.as_view(), name='my_profile'),
url(r'^(?P<pk>[0-9]+)/profile$', AccountDetail.as_view(), name='user_detail'),
# url(r'^profile/$', AccountDetail.as_view(), name='user_detail'),
]
from django.contrib.auth import get_user_model
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
from django.shortcuts import render
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView
from django.views.generic import DetailView
from django.views.generic.edit import CreateView
from django_tables2 import SingleTableView
from django.urls import reverse_lazy
from core.mixins import ManageAccountAuthMixin
from .filters import UserFilter
from .forms import UserCreateForm, PasswordUpdateForm
from .models import User
from .models import Profile
from .tables import UserTable
from .filters import UserFilter
class ManageAccountAuthMixin(LoginRequiredMixin):
'''
Classe mixin de vérification que l'utilisateur possède les droits de créer le compte
'''
def dispatch(self, request, *args, **kwargs):
user = request.user
if user.is_authenticated and (
user.is_resp or user.access_all_data or user.edit_all_data or user.is_staff or user.is_superuser):
print('access granted')
return super(ManageAccountAuthMixin, self).dispatch(request, *args, **kwargs)
else:
return redirect('home')
class UserCreate(ManageAccountAuthMixin, CreateView):
......@@ -35,7 +21,7 @@ class UserCreate(ManageAccountAuthMixin, CreateView):
User Creation View
"""
model = User
model = Profile
form_class = UserCreateForm
template_name = 'leaflet_form.html'
......@@ -74,36 +60,16 @@ def change_password(request):
class UserListView(ManageAccountAuthMixin, SingleTableView):
table_class = UserTable
model = User
model = Profile
template_name = 'accounts/user_list.html'
filterset_class = UserFilter
# def get_queryset(self):
# loggeduser = self.request.user
# userdetail = get_user_model().objects.get(id=loggeduser.id)
# if userdetail.access_all_data == True:
# print('Chargement de la carte pour l\'utilisateur avec accès total')
# new_context = User.objects.all()
# elif userdetail.is_resp:
# print('Chargement de la carte pour le Coordinateur')
# print(userdetail.id)
# print(loggeduser.id)
# coordterritory = userdetail.coord_territory.all()
# new_context = Place.objects.filter(Q(territory__in=coordterritory) | (
# (~Q(territory__in=coordterritory) & (
# Q(hidden=False) | (
# Q(hidden=True) & (Q(creator=loggeduser) | Q(authorized_user=loggeduser)))))))
# else:
# print('Chargement de la carte pour l\'obs lambda')
# new_context = Place.objects.filter(
# Q(hidden=False) | (Q(hidden=True) & Q(authorized_user__username__contains=loggeduser.username)))
# return new_context
def get_queryset(self):
"""Returns accounts ordered by update timestamp"""
return User.objects.all().order_by('-timestamp_update')
class AccountDetail(LoginRequiredMixin, TemplateView):
template_name = 'accounts/profile.html'
\ No newline at end of file
class AccountDetail(LoginRequiredMixin, DetailView):
model = get_user_model()
template_name = 'accounts/profile.html'
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-18 16:13
from __future__ import unicode_literals
import ckeditor.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Actu',
fields=[
('id_actu', models.AutoField(primary_key=True, serialize=False)),
('title', models.CharField(max_length=200)),
('briefdescr', models.TextField(max_length=2048)),
('text', ckeditor.fields.RichTextField()),
('timestamp_create', models.DateTimeField(auto_now_add=True)),
('timestamp_update', models.DateTimeField(auto_now=True)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'Actualités du site',
'verbose_name': 'Actualité du site',
},
),
]
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'core'
"""
Mixins du projet
"""
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect
class ModifyDataAuthMixin:
'''
Classe mixin de vérification que l'utilisateur possède les droits de modifier la donnée:
Soit il est créateur de la donnée,
Soit il dispose de l'authorisation de modifier toutes les données ( :get_user_model():access_all_data is True)
'''
def dispatch(self, request, *args, **kwargs):
self.obj = self.get_object()
if get_user_model().objects.filter(id=request.user.id).filter(
edit_all_data=True) or self.obj.creator == request.user:
print('access granted')
return super(ModifyDataAuthMixin, self).dispatch(request, *args, **kwargs)
else:
return redirect('unauthorized_modification')
class ManageAccountAuthMixin(LoginRequiredMixin):
'''
Classe mixin de vérification que l'utilisateur possède les droits de créer le compte
'''
def dispatch(self, request, *args, **kwargs):
user = request.user
if user.is_authenticated and (
user.is_resp or user.access_all_data or user.edit_all_data or user.is_staff or user.is_superuser):
print('access granted')
return super(ManageAccountAuthMixin, self).dispatch(request, *args, **kwargs)
else:
return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
from django.db import models
# Create your models here.
from django.test import TestCase
# Create your tests here.
from django.shortcuts import render
# Create your views here.
......@@ -25,6 +25,8 @@ with open(os.path.join(BASE_DIR, 'secretkey.txt')) as f: