Commit a8dbe462 authored by Hugo LEVY-FALK's avatar Hugo LEVY-FALK

Release : 2.7

parents ef4e430e 833d7177
Pipeline #955 failed with stage
settings_local.py # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*.swp *.swp
*.pyc
# Translations
*.mo
*.pot
# Django stuff
*.log
local_settings.py
db.sqlite3
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# PyCharm project settings
.idea/
# Django statics
static_files/
static/logo/
# re2o specific
settings_local.py
re2o.png re2o.png
__pycache__/* media/
static_files/*
static/logo/*
media/*
...@@ -150,3 +150,31 @@ On some database engines (postgreSQL) you also need to update the id sequences: ...@@ -150,3 +150,31 @@ On some database engines (postgreSQL) you also need to update the id sequences:
```bash ```bash
python3 manage.py sqlsequencereset cotisations | python3 manage.py dbshell python3 manage.py sqlsequencereset cotisations | python3 manage.py dbshell
``` ```
## MR 296: Frontend changes
Install fonts-font-awesome
```bash
apt-get -y install fonts-font-awesome
```
Collec new statics
```bash
python3 manage.py collectstatic
```
## MR 391: Document templates and subscription vouchers
Re2o can now use templates for generated invoices. To load default templates run
```bash
./install update
```
Be carefull, you need the proper rights to edit a DocumentTemplate.
Re2o now sends subscription voucher when an invoice is controlled. It uses one
of the templates. You also need to set the name of the president of your association
to be set in your settings.
# Re2o # Re2o
Gnu public license v2.0 GNU public license v2.0
## Avant propos ## Avant propos
Re2o est un logiciel d'administration développé initiallement au rezometz. Il Re2o est un logiciel d'administration développé initialement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en se veut agnostique au réseau considéré, de manière à être installable en
quelques clics. quelques clics.
...@@ -31,15 +31,15 @@ Pour cela : ...@@ -31,15 +31,15 @@ Pour cela :
## Fonctionnement général ## Fonctionnement général
Re2o est séparé entre les models, qui sont visible sur le schéma des Re2o est séparé entre les models, qui sont visibles sur le schéma des
dépendances. Il s'agit en réalité des tables sql, et les fields etant les dépendances. Il s'agit en réalité des tables sql, et les fields étant les
colonnes. colonnes.
Ceci dit il n'est jamais nécessaire de toucher directement au sql, django Ceci dit il n'est jamais nécessaire de toucher directement au sql, django
procédant automatiquement à tout cela. procédant automatiquement à tout cela.
On crée donc différents models (user, right pour les droits des users, On crée donc différents models (user, right pour les droits des users,
interfaces, IpList pour l'ensemble des adresses ip, etc) interfaces, IpList pour l'ensemble des adresses ip, etc)
Du coté des forms, il s'agit des formulaire d'édition des models. Il Du coté des forms, il s'agit des formulaires d'édition des models. Il
s'agit de ModelForms django, qui héritent des models très simplement, voir la s'agit de ModelForms django, qui héritent des models très simplement, voir la
documentation django models forms. documentation django models forms.
...@@ -56,12 +56,20 @@ d'accéder à ces vues, utilisé par re2o-tools. ...@@ -56,12 +56,20 @@ d'accéder à ces vues, utilisé par re2o-tools.
# Requète en base de donnée # Requète en base de donnée
Pour avoir un shell, il suffit de lancer '''python3 manage.py shell''' Pour avoir un shell, lancer :
Pour charger des objets, example avec User, faire : ```.bash
''' from users.models import User''' python3 manage.py shell
Pour charger les objets django, il suffit de faire User.objects.all() ```
pour tous les users par exemple.
Il est ensuite aisé de faire des requètes, par exemple Pour charger des objets (exemple avec User), faire :
User.objects.filter(pseudo='test') ```.python
Des exemples et la documentation complète sur les requètes django sont from users.models import User
```
Pour charger les objets django, il suffit de faire `User.objects.all()`
pour tous les users par exemple.
Il est ensuite aisé de faire des requêtes, par exemple
`User.objects.filter(pseudo='test')`
Des exemples et la documentation complète sur les requêtes django sont
disponible sur le site officiel. disponible sur le site officiel.
...@@ -26,9 +26,9 @@ done. ...@@ -26,9 +26,9 @@ done.
""" """
from django.conf import settings from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.utils.translation import ugettext_lazy as _ from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
def _create_api_permission(): def _create_api_permission():
...@@ -71,4 +71,5 @@ def can_view(user): ...@@ -71,4 +71,5 @@ def can_view(user):
'codename': settings.API_PERMISSION_CODENAME 'codename': settings.API_PERMISSION_CODENAME
} }
can = user.has_perm('%(app_label)s.%(codename)s' % kwargs) can = user.has_perm('%(app_label)s.%(codename)s' % kwargs)
return can, None if can else _("You cannot see this application.") return can, None if can else _("You don't have the right to see this"
" application.")
...@@ -26,12 +26,14 @@ import datetime ...@@ -26,12 +26,14 @@ import datetime
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from rest_framework.authentication import TokenAuthentication
from rest_framework import exceptions from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication
class ExpiringTokenAuthentication(TokenAuthentication): class ExpiringTokenAuthentication(TokenAuthentication):
"""Authenticate a user if the provided token is valid and not expired. """Authenticate a user if the provided token is valid and not expired.
""" """
def authenticate_credentials(self, key): def authenticate_credentials(self, key):
"""See base class. Add the verification the token is not expired. """See base class. Add the verification the token is not expired.
""" """
...@@ -44,6 +46,6 @@ class ExpiringTokenAuthentication(TokenAuthentication): ...@@ -44,6 +46,6 @@ class ExpiringTokenAuthentication(TokenAuthentication):
) )
utc_now = datetime.datetime.now(datetime.timezone.utc) utc_now = datetime.datetime.now(datetime.timezone.utc)
if token.created < utc_now - token_duration: if token.created < utc_now - token_duration:
raise exceptions.AuthenticationFailed(_('Token has expired')) raise exceptions.AuthenticationFailed(_("The token has expired."))
return (token.user, token) return token.user, token
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2018 Maël Kervella
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
msgid ""
msgstr ""
"Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-08 23:06+0100\n"
"PO-Revision-Date: 2019-01-07 01:37+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: acl.py:74
msgid "You don't have the right to see this application."
msgstr "Vous n'avez pas le droit de voir cette application."
#: authentication.py:49
msgid "The token has expired."
msgstr "Le jeton a expiré."
...@@ -24,8 +24,6 @@ ...@@ -24,8 +24,6 @@
from rest_framework import permissions, exceptions from rest_framework import permissions, exceptions
from re2o.acl import can_create, can_edit, can_delete, can_view_all
from . import acl from . import acl
...@@ -57,14 +55,14 @@ def _get_param_in_view(view, param_name): ...@@ -57,14 +55,14 @@ def _get_param_in_view(view, param_name):
AssertionError: None of the getter function or the attribute are AssertionError: None of the getter function or the attribute are
defined in the view. defined in the view.
""" """
assert hasattr(view, 'get_'+param_name) \ assert hasattr(view, 'get_' + param_name) \
or getattr(view, param_name, None) is not None, ( or getattr(view, param_name, None) is not None, (
'cannot apply {} on a view that does not set ' 'cannot apply {} on a view that does not set '
'`.{}` or have a `.get_{}()` method.' '`.{}` or have a `.get_{}()` method.'
).format(self.__class__.__name__, param_name, param_name) ).format(self.__class__.__name__, param_name, param_name)
if hasattr(view, 'get_'+param_name): if hasattr(view, 'get_' + param_name):
param = getattr(view, 'get_'+param_name)() param = getattr(view, 'get_' + param_name)()
assert param is not None, ( assert param is not None, (
'{}.get_{}() returned None' '{}.get_{}() returned None'
).format(view.__class__.__name__, param_name) ).format(view.__class__.__name__, param_name)
...@@ -80,7 +78,8 @@ class ACLPermission(permissions.BasePermission): ...@@ -80,7 +78,8 @@ class ACLPermission(permissions.BasePermission):
See the wiki for the syntax of this attribute. See the wiki for the syntax of this attribute.
""" """
def get_required_permissions(self, method, view): @staticmethod
def get_required_permissions(method, view):
"""Build the list of permissions required for the request to be """Build the list of permissions required for the request to be
accepted. accepted.
...@@ -153,15 +152,15 @@ class AutodetectACLPermission(permissions.BasePermission): ...@@ -153,15 +152,15 @@ class AutodetectACLPermission(permissions.BasePermission):
'OPTIONS': [can_see_api, lambda model: model.can_view_all], 'OPTIONS': [can_see_api, lambda model: model.can_view_all],
'HEAD': [can_see_api, lambda model: model.can_view_all], 'HEAD': [can_see_api, lambda model: model.can_view_all],
'POST': [can_see_api, lambda model: model.can_create], 'POST': [can_see_api, lambda model: model.can_create],
'PUT': [], # No restrictions, apply to objects 'PUT': [], # No restrictions, apply to objects
'PATCH': [], # No restrictions, apply to objects 'PATCH': [], # No restrictions, apply to objects
'DELETE': [], # No restrictions, apply to objects 'DELETE': [], # No restrictions, apply to objects
} }
perms_obj_map = { perms_obj_map = {
'GET': [can_see_api, lambda obj: obj.can_view], 'GET': [can_see_api, lambda obj: obj.can_view],
'OPTIONS': [can_see_api, lambda obj: obj.can_view], 'OPTIONS': [can_see_api, lambda obj: obj.can_view],
'HEAD': [can_see_api, lambda obj: obj.can_view], 'HEAD': [can_see_api, lambda obj: obj.can_view],
'POST': [], # No restrictions, apply to models 'POST': [], # No restrictions, apply to models
'PUT': [can_see_api, lambda obj: obj.can_edit], 'PUT': [can_see_api, lambda obj: obj.can_edit],
'PATCH': [can_see_api, lambda obj: obj.can_edit], 'PATCH': [can_see_api, lambda obj: obj.can_edit],
'DELETE': [can_see_api, lambda obj: obj.can_delete], 'DELETE': [can_see_api, lambda obj: obj.can_delete],
...@@ -209,7 +208,8 @@ class AutodetectACLPermission(permissions.BasePermission): ...@@ -209,7 +208,8 @@ class AutodetectACLPermission(permissions.BasePermission):
return [perm(obj) for perm in self.perms_obj_map[method]] return [perm(obj) for perm in self.perms_obj_map[method]]
def _queryset(self, view): @staticmethod
def _queryset(view):
return _get_param_in_view(view, 'queryset') return _get_param_in_view(view, 'queryset')
def has_permission(self, request, view): def has_permission(self, request, view):
...@@ -282,4 +282,3 @@ class AutodetectACLPermission(permissions.BasePermission): ...@@ -282,4 +282,3 @@ class AutodetectACLPermission(permissions.BasePermission):
return False return False
return True return True
...@@ -24,12 +24,12 @@ ...@@ -24,12 +24,12 @@
from collections import OrderedDict from collections import OrderedDict
from django.conf.urls import url, include from django.conf.urls import url
from django.core.urlresolvers import NoReverseMatch from django.core.urlresolvers import NoReverseMatch
from rest_framework import views from rest_framework import views
from rest_framework.routers import DefaultRouter
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from rest_framework.routers import DefaultRouter
from rest_framework.schemas import SchemaGenerator from rest_framework.schemas import SchemaGenerator
from rest_framework.settings import api_settings from rest_framework.settings import api_settings
...@@ -64,7 +64,8 @@ class AllViewsRouter(DefaultRouter): ...@@ -64,7 +64,8 @@ class AllViewsRouter(DefaultRouter):
name = self.get_default_name(pattern) name = self.get_default_name(pattern)
self.view_registry.append((pattern, view, name)) self.view_registry.append((pattern, view, name))
def get_default_name(self, pattern): @staticmethod
def get_default_name(pattern):
"""Returns the name to use for the route if none was specified. """Returns the name to use for the route if none was specified.
Args: Args:
...@@ -113,7 +114,8 @@ class AllViewsRouter(DefaultRouter): ...@@ -113,7 +114,8 @@ class AllViewsRouter(DefaultRouter):
_ignore_model_permissions = True _ignore_model_permissions = True
renderer_classes = view_renderers renderer_classes = view_renderers
def get(self, request, *args, **kwargs): @staticmethod
def get(request, *args, **kwargs):
if request.accepted_renderer.media_type in schema_media_types: if request.accepted_renderer.media_type in schema_media_types:
# Return a schema response. # Return a schema response.
schema = schema_generator.get_schema(request) schema = schema_generator.get_schema(request)
......
This diff is collapsed.
...@@ -48,4 +48,4 @@ API_APPS = ( ...@@ -48,4 +48,4 @@ API_APPS = (
) )
# The expiration time for an authentication token # The expiration time for an authentication token
API_TOKEN_DURATION = 86400 # 24 hours API_TOKEN_DURATION = 86400 # 24 hours
...@@ -21,10 +21,11 @@ ...@@ -21,10 +21,11 @@
"""Defines the test suite for the API """Defines the test suite for the API
""" """
import json
import datetime import datetime
from rest_framework.test import APITestCase import json
from requests import codes from requests import codes
from rest_framework.test import APITestCase
import cotisations.models as cotisations import cotisations.models as cotisations
import machines.models as machines import machines.models as machines
...@@ -33,7 +34,7 @@ import topologie.models as topologie ...@@ -33,7 +34,7 @@ import topologie.models as topologie
import users.models as users import users.models as users
class APIEndpointsTestCase(APITestCase): class APIEndpointsTestCase(APITestCase):
"""Test case to test that all endpoints are reachable with respects to """Test case to test that all endpoints are reachable with respects to
authentication and permission checks. authentication and permission checks.
...@@ -148,10 +149,10 @@ class APIEndpointsTestCase(APITestCase): ...@@ -148,10 +149,10 @@ class APIEndpointsTestCase(APITestCase):
'/api/users/club/', '/api/users/club/',
# 4th user to be create (stduser, superuser, users_adherent_1, # 4th user to be create (stduser, superuser, users_adherent_1,
# users_club_1) # users_club_1)
'/api/users/club/4/', '/api/users/club/4/',
'/api/users/listright/', '/api/users/listright/',
# TODO: Merge !145 # TODO: Merge !145
# '/api/users/listright/1/', # '/api/users/listright/1/',
'/api/users/school/', '/api/users/school/',
'/api/users/school/1/', '/api/users/school/1/',
'/api/users/serviceuser/', '/api/users/serviceuser/',
...@@ -215,7 +216,7 @@ class APIEndpointsTestCase(APITestCase): ...@@ -215,7 +216,7 @@ class APIEndpointsTestCase(APITestCase):
'/api/users/user/4242/', '/api/users/user/4242/',
'/api/users/whitelist/4242/', '/api/users/whitelist/4242/',
] ]
stduser = None stduser = None
superuser = None superuser = None
...@@ -363,7 +364,7 @@ class APIEndpointsTestCase(APITestCase): ...@@ -363,7 +364,7 @@ class APIEndpointsTestCase(APITestCase):
machine=cls.machines_machine_1, # Dep machines.Machine machine=cls.machines_machine_1, # Dep machines.Machine
type=cls.machines_machinetype_1, # Dep machines.MachineType type=cls.machines_machinetype_1, # Dep machines.MachineType
details="machines Interface 1", details="machines Interface 1",
#port_lists=[cls.machines_ouvertureportlist_1] # Dep machines.OuverturePortList # port_lists=[cls.machines_ouvertureportlist_1] # Dep machines.OuverturePortList
) )
cls.machines_domain_1 = machines.Domain.objects.create( cls.machines_domain_1 = machines.Domain.objects.create(
interface_parent=cls.machines_interface_1, # Dep machines.Interface interface_parent=cls.machines_interface_1, # Dep machines.Interface
...@@ -525,14 +526,14 @@ class APIEndpointsTestCase(APITestCase): ...@@ -525,14 +526,14 @@ class APIEndpointsTestCase(APITestCase):
uid_number=21103, uid_number=21103,
rezo_rez_uid=21103 rezo_rez_uid=21103
) )
# Need merge of MR145 to work # Need merge of MR145 to work
# TODO: Merge !145 # TODO: Merge !145
# cls.users_listright_1 = users.ListRight.objects.create( # cls.users_listright_1 = users.ListRight.objects.create(
# unix_name="userslistright", # unix_name="userslistright",
# gid=601, # gid=601,
# critical=False, # critical=False,
# details="userslistright" # details="userslistright"
# ) # )
cls.users_serviceuser_1 = users.ServiceUser.objects.create( cls.users_serviceuser_1 = users.ServiceUser.objects.create(
password="password", password="password",
last_login=datetime.datetime.now(datetime.timezone.utc), last_login=datetime.datetime.now(datetime.timezone.utc),
...@@ -663,7 +664,7 @@ class APIEndpointsTestCase(APITestCase): ...@@ -663,7 +664,7 @@ class APIEndpointsTestCase(APITestCase):
AssertionError: An endpoint did not have a 200 status code. AssertionError: An endpoint did not have a 200 status code.
""" """
self.client.force_authenticate(user=self.superuser) self.client.force_authenticate(user=self.superuser)
urls = self.no_auth_endpoints + self.auth_no_perm_endpoints + \ urls = self.no_auth_endpoints + self.auth_no_perm_endpoints + \
self.auth_perm_endpoints self.auth_perm_endpoints
...@@ -676,6 +677,7 @@ class APIEndpointsTestCase(APITestCase): ...@@ -676,6 +677,7 @@ class APIEndpointsTestCase(APITestCase):
formats=[None, 'json', 'api'], formats=[None, 'json', 'api'],
assert_more=assert_more) assert_more=assert_more)
class APIPaginationTestCase(APITestCase): class APIPaginationTestCase(APITestCase):
"""Test case to check that the pagination is used on all endpoints that """Test case to check that the pagination is used on all endpoints that
should use it. should use it.
...@@ -756,7 +758,7 @@ class APIPaginationTestCase(APITestCase): ...@@ -756,7 +758,7 @@ class APIPaginationTestCase(APITestCase):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
cls.superuser.delete() cls.superuser.delete()
super().tearDownClass() super(APIPaginationTestCase, self).tearDownClass()
def test_pagination(self): def test_pagination(self):
"""Tests that every endpoint is using the pagination correctly. """Tests that every endpoint is using the pagination correctly.
...@@ -776,4 +778,3 @@ class APIPaginationTestCase(APITestCase): ...@@ -776,4 +778,3 @@ class APIPaginationTestCase(APITestCase):
assert 'previous' in res_json.keys() assert 'previous' in res_json.keys()
assert 'results' in res_json.keys() assert 'results' in res_json.keys()
assert not len('results') > 100 assert not len('results') > 100
...@@ -32,7 +32,6 @@ from django.conf.urls import url, include ...@@ -32,7 +32,6 @@ from django.conf.urls import url, include
from . import views from . import views
from .routers import AllViewsRouter from .routers import AllViewsRouter
router = AllViewsRouter() router = AllViewsRouter()
# COTISATIONS # COTISATIONS
router.register_viewset(r'cotisations/facture', views.FactureViewSet) router.register_viewset(r'cotisations/facture', views.FactureViewSet)
...@@ -63,10 +62,12 @@ router.register_viewset(r'machines/service', views.ServiceViewSet) ...@@ -63,10 +62,12 @@ router.register_viewset(r'machines/service', views.ServiceViewSet)
router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink') router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink')
router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet) router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet)
router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet) router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet)
router.register_viewset(r'machines/role', views.RoleViewSet)
# PREFERENCES # PREFERENCES
router.register_view(r'preferences/optionaluser', views.OptionalUserView), router.register_view(r'preferences/optionaluser', views.OptionalUserView),
router.register_view(r'preferences/optionalmachine', views.OptionalMachineView), router.register_view(r'preferences/optionalmachine', views.OptionalMachineView),
router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView), router.register_view(r'preferences/optionaltopologie', views.OptionalTopologieView),
router.register_view(r'preferences/radiusoption', views.RadiusOptionView),
router.register_view(r'preferences/generaloption', views.GeneralOptionView), router.register_view(r'preferences/generaloption', views.GeneralOptionView),
router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'), router.register_viewset(r'preferences/service', views.HomeServiceViewSet, base_name='homeservice'),
router.register_view(r'preferences/assooption', views.AssoOptionView), router.register_view(r'preferences/assooption', views.AssoOptionView),
...@@ -81,12 +82,15 @@ router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet) ...@@ -81,12 +82,15 @@ router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet)
router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet) router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet)
router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet) router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet)
router.register_viewset(r'topologie/building', views.BuildingViewSet) router.register_viewset(r'topologie/building', views.BuildingViewSet)
router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport') router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
router.register_viewset(r'topologie/portprofile', views.PortProfileViewSet, base_name='portprofile')
router.register_viewset(r'topologie/room', views.RoomViewSet) router.register_viewset(r'topologie/room', views.RoomViewSet)
router.register(r'topologie/portprofile', views.PortProfileViewSet) router.register(r'topologie/portprofile', views.PortProfileViewSet)
# USERS # USERS
router.register_viewset(r'users/user', views.UserViewSet) router.register_viewset(r'users/user', views.UserViewSet, base_name='user')
router.register_viewset(r'users/homecreation', views.HomeCreationViewSet) router.register_viewset(r'users/homecreation', views.HomeCreationViewSet, base_name='homecreation')
router.register_viewset(r'users/normaluser', views.NormalUserViewSet, base_name='normaluser')