URL refactoring
This commit is contained in:
parent
3d7727b4b4
commit
253da87a99
|
|
@ -6,6 +6,6 @@ from account.views import common as views
|
||||||
app_name = 'account'
|
app_name = 'account'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('user/', views.UserView.as_view(), name='user_get_update'),
|
path('user/', views.UserView.as_view(), name='user-get-update'),
|
||||||
path('device/', views.FCMDeviceViewSet.as_view(), name='fcm_device_create'),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@ from advertisement.views import web as views
|
||||||
app_name = 'advertisement'
|
app_name = 'advertisement'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('list/', views.AdvertisementListView.as_view(), name='advertisements_list')
|
path('', views.AdvertisementListView.as_view(), name='list')
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,36 +6,7 @@ from collection.views import common as views
|
||||||
app_name = 'collection'
|
app_name = 'collection'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('list/', views.CollectionListView.as_view(),
|
path('', views.CollectionListView.as_view(), name='list'),
|
||||||
name='collections_list'),
|
path('items/', views.CollectionItemListView.as_view(), name='collection-items-list'),
|
||||||
# path('create/', views.CollectionCreateView.as_view(),
|
path('guides/', views.GuideListView.as_view(), name='guides-list'),
|
||||||
# name='collection_create'),
|
|
||||||
# path('<int:pk>/', views.CollectionRetrieveView.as_view(),
|
|
||||||
# name='collection_retrieve'),
|
|
||||||
# path('<int:pk>/delete/', views.CollectionDestroyView.as_view(),
|
|
||||||
# name='collection_destroy'),
|
|
||||||
# path('<int:pk>/update/', views.CollectionUpdateView.as_view(),
|
|
||||||
# name='collection_update'),
|
|
||||||
|
|
||||||
path('collection_items/', views.CollectionItemListView.as_view(),
|
|
||||||
name='collection_items_list'),
|
|
||||||
# path('collection_items/create/', views.CollectionItemCreateView.as_view(),
|
|
||||||
# name='collection_items_create'),
|
|
||||||
# path('collection_items/<int:pk>/', views.CollectionItemRetrieveView.as_view(),
|
|
||||||
# name='collection_items_retrieve'),
|
|
||||||
# path('collection_items/<int:pk>/delete/', views.CollectionDestroyView.as_view(),
|
|
||||||
# name='collection_items_destroy'),
|
|
||||||
# path('collection_items/<int:pk>/update/', views.CollectionItemUpdateView.as_view(),
|
|
||||||
# name='collection_items_update'),
|
|
||||||
|
|
||||||
path('guides/', views.GuideListView.as_view(),
|
|
||||||
name='guides_list'),
|
|
||||||
# path('guides/create/', views.GuideCreateView.as_view(),
|
|
||||||
# name='guide_create'),
|
|
||||||
# path('guides/<int:pk>/', views.GuideRetrieveView.as_view(),
|
|
||||||
# name='guide_retrieve'),
|
|
||||||
# path('guides/<int:pk>/delete/', views.GuideDestroyView.as_view(),
|
|
||||||
# name='guide_destroy'),
|
|
||||||
# path('guides/<int:pk>/update/', views.GuideUpdateView.as_view(),
|
|
||||||
# name='guide_update'),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
"""Establishment admin conf."""
|
"""Establishment admin conf."""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from establishment import models
|
from establishment import models
|
||||||
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
|
from main.models import Award
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.EstablishmentType)
|
@admin.register(models.EstablishmentType)
|
||||||
|
|
@ -13,6 +15,12 @@ class EstablishmentSubTypeAdmin(admin.ModelAdmin):
|
||||||
"""EstablishmentSubType admin."""
|
"""EstablishmentSubType admin."""
|
||||||
|
|
||||||
|
|
||||||
|
class AwardInline(GenericTabularInline):
|
||||||
|
model = Award
|
||||||
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Establishment)
|
@admin.register(models.Establishment)
|
||||||
class EstablishmentAdmin(admin.ModelAdmin):
|
class EstablishmentAdmin(admin.ModelAdmin):
|
||||||
"""Establishment admin."""
|
"""Establishment admin."""
|
||||||
|
inlines = [AwardInline, ]
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,5 @@ from . import views
|
||||||
app_name = 'gallery'
|
app_name = 'gallery'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('upload/', views.ImageUploadView.as_view(),
|
path('upload/', views.ImageUploadView.as_view(), name='upload-image')
|
||||||
name='upload_image')
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,15 @@ from location import views
|
||||||
app_name = 'location'
|
app_name = 'location'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('country/', views.CountryListView.as_view(), name='country-list'),
|
path('addresses/', views.AddressListView.as_view(), name='address-list'),
|
||||||
path('country/<int:pk>/', views.CountryRetrieveView.as_view(),
|
path('addresses/<int:pk>/', views.AddressRetrieveView.as_view(), name='address-retrieve'),
|
||||||
name='country-retrieve'),
|
|
||||||
|
|
||||||
path('region/', views.RegionListView.as_view(), name='region_list'),
|
path('cities/', views.CityListView.as_view(), name='city-list'),
|
||||||
path('region/create/', views.RegionCreateView.as_view(), name='region_create'),
|
path('cities/<int:pk>/', views.CityRetrieveView.as_view(), name='city-retrieve'),
|
||||||
path('region/<int:pk>/', views.RegionRetrieveView.as_view(), name='region_retrieve'),
|
|
||||||
path('region/<int:pk>/delete/', views.RegionDestroyView.as_view(), name='region_destroy'),
|
|
||||||
path('region/<int:pk>/update/', views.RegionUpdateView.as_view(), name='region_update'),
|
|
||||||
|
|
||||||
path('city/', views.CityListView.as_view(), name='city_list'),
|
path('countries/', views.CountryListView.as_view(), name='country-list'),
|
||||||
path('city/create/', views.CityCreateView.as_view(), name='city_create'),
|
path('countries/<int:pk>/', views.CountryRetrieveView.as_view(), name='country-retrieve'),
|
||||||
path('city/<int:pk>/', views.CityRetrieveView.as_view(), name='city_retrieve'),
|
|
||||||
path('city/<int:pk>/delete/', views.CityDestroyView.as_view(), name='city_destroy'),
|
|
||||||
path('city/<int:pk>/update/', views.CityUpdateView.as_view(), name='city_update'),
|
|
||||||
|
|
||||||
path('address/', views.AddressListView.as_view(), name='address_list'),
|
path('regions/', views.RegionListView.as_view(), name='region-list'),
|
||||||
path('address/create/', views.AddressCreateView.as_view(), name='address_create'),
|
path('regions/<int:pk>/', views.RegionRetrieveView.as_view(), name='region-retrieve'),
|
||||||
path('address/<int:pk>/', views.AddressRetrieveView.as_view(), name='address_retrieve'),
|
|
||||||
path('address/<int:pk>/delete/', views.AddressDestroyView.as_view(), name='address_destroy'),
|
|
||||||
path('address/<int:pk>/update/', views.AddressUpdateView.as_view(), name='address_update'),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -209,3 +209,6 @@ class AwardType(models.Model):
|
||||||
country = models.ForeignKey(
|
country = models.ForeignKey(
|
||||||
'location.Country', verbose_name=_('country'), on_delete=models.CASCADE)
|
'location.Country', verbose_name=_('country'), on_delete=models.CASCADE)
|
||||||
name = models.CharField(_('name'), max_length=255)
|
name = models.CharField(_('name'), max_length=255)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,8 @@ from main import views
|
||||||
app = 'main'
|
app = 'main'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('determine-site/', views.DetermineSiteView.as_view(),
|
path('determine-site/', views.DetermineSiteView.as_view(), name='determine-site'),
|
||||||
name='determine-site'),
|
path('site-settings/<subdomain>/', views.SiteSettingsView.as_view(), name='site-settings'),
|
||||||
path('site-settings/<subdomain>/', views.SiteSettingsView.as_view(),
|
path('awards/', views.AwardView.as_view(), name='awards_list'),
|
||||||
name='site-settings', ),
|
path('awards/<int:pk>/', views.AwardRetrieveView.as_view(), name='awards_retrieve'),
|
||||||
# path('features/', views.FeaturesLCView.as_view(),
|
|
||||||
# name='features-lc'),
|
|
||||||
# path('features/<int:pk>/', views.FeaturesRUDView.as_view(),
|
|
||||||
# name='features-rud'),
|
|
||||||
# path('site-features/', views.SiteFeaturesLCView.as_view(),
|
|
||||||
# name='site-features-lc'),
|
|
||||||
# path('site-features/<int:pk>/', views.SiteFeaturesRUDView.as_view(),
|
|
||||||
# name='site-features-rud'),
|
|
||||||
path('awards/', views.AwardView.as_view(), name='awards_list', ),
|
|
||||||
path('awards/<int:pk>/', views.AwardRetrieveView.as_view(), name='awards_retrieve', ),
|
|
||||||
]
|
]
|
||||||
|
|
@ -69,7 +69,7 @@ class AwardView(generics.ListAPIView):
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
|
||||||
|
|
||||||
class AwardRetrieveView(generics.RetrieveUpdateDestroyAPIView):
|
class AwardRetrieveView(generics.RetrieveAPIView):
|
||||||
"""Award retrieve view."""
|
"""Award retrieve view."""
|
||||||
serializer_class = serializers.AwardSerializer
|
serializer_class = serializers.AwardSerializer
|
||||||
queryset = models.Award.objects.all()
|
queryset = models.Award.objects.all()
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,7 @@ from news.views import common
|
||||||
app_name = 'news'
|
app_name = 'news'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', common.NewsList.as_view(), name='news_list'),
|
path('', common.NewsListView.as_view(), name='list'),
|
||||||
path('create/', common.NewsCreate.as_view(), name='news_create'),
|
path('<int:pk>/', common.NewsDetailView.as_view(), name='rud'),
|
||||||
path('<int:pk>/', common.NewsDetail.as_view(), name='news_detail'),
|
path('type/', common.NewsTypeListView.as_view(), name='type'),
|
||||||
path('<int:pk>/update/', common.NewsUpdate.as_view(), name='news_update'),
|
|
||||||
path('<int:pk>/delete/', common.NewsDelete.as_view(), name='news_delete'),
|
|
||||||
path('type/', common.NewsTypeList.as_view(), name='news_type'),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class NewsViewMixin(JWTGenericViewMixin):
|
||||||
return News.objects.annotate_localized_fields(locale=self.request.locale)
|
return News.objects.annotate_localized_fields(locale=self.request.locale)
|
||||||
|
|
||||||
|
|
||||||
class NewsList(NewsViewMixin, JWTListAPIView):
|
class NewsListView(NewsViewMixin, JWTListAPIView):
|
||||||
"""News list view."""
|
"""News list view."""
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
serializer_class = serializers.NewsSerializer
|
serializer_class = serializers.NewsSerializer
|
||||||
|
|
@ -30,32 +30,20 @@ class NewsList(NewsViewMixin, JWTListAPIView):
|
||||||
.order_by('-is_highlighted', '-created')
|
.order_by('-is_highlighted', '-created')
|
||||||
|
|
||||||
|
|
||||||
class NewsCreate(generics.CreateAPIView):
|
# class NewsCreateView(generics.CreateAPIView):
|
||||||
"""News list view."""
|
# """News list view."""
|
||||||
queryset = News.objects.all()
|
# queryset = News.objects.all()
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
# permission_classes = (permissions.IsAuthenticated, )
|
||||||
serializer_class = serializers.NewsCreateUpdateSerializer
|
# serializer_class = serializers.NewsCreateUpdateSerializer
|
||||||
|
|
||||||
|
|
||||||
class NewsDetail(NewsViewMixin, generics.RetrieveAPIView):
|
class NewsDetailView(NewsViewMixin, generics.RetrieveAPIView):
|
||||||
"""News detail view."""
|
"""News detail view."""
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
|
||||||
serializer_class = serializers.NewsSerializer
|
serializer_class = serializers.NewsSerializer
|
||||||
|
|
||||||
|
|
||||||
class NewsDelete(generics.DestroyAPIView):
|
class NewsTypeListView(generics.ListAPIView):
|
||||||
"""News delete view."""
|
|
||||||
queryset = News.objects.all()
|
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
|
||||||
|
|
||||||
|
|
||||||
class NewsUpdate(NewsViewMixin, generics.UpdateAPIView):
|
|
||||||
"""News update view."""
|
|
||||||
permission_classes = (permissions.IsAuthenticated, )
|
|
||||||
serializer_class = serializers.NewsCreateUpdateSerializer
|
|
||||||
|
|
||||||
|
|
||||||
class NewsTypeList(generics.ListAPIView):
|
|
||||||
"""NewsType list view."""
|
"""NewsType list view."""
|
||||||
serializer_class = serializers.NewsTypeSerializer
|
serializer_class = serializers.NewsTypeSerializer
|
||||||
permission_classes = (permissions.AllowAny, )
|
permission_classes = (permissions.AllowAny, )
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,5 @@ from partner.views import common as views
|
||||||
app_name = 'partner'
|
app_name = 'partner'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('list/', views.PartnerListView.as_view(),
|
path('', views.PartnerListView.as_view(), name='list')
|
||||||
name='partner_list')
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,9 @@ from translation import views
|
||||||
app_name = 'translation'
|
app_name = 'translation'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('languages/', views.LanguageListCreateView.as_view(),
|
path('languages/', views.LanguageListView.as_view(), name='languages'),
|
||||||
name='languages'),
|
path('languages/<int:pk>/', views.LanguageRetrieveView.as_view(), name='language'),
|
||||||
path('languages/<int:pk>/', views.LanguageRetrieveUpdateDestroyView.as_view(),
|
path('site/', views.SiteInterfaceDictionaryView.as_view(), name='site-interface'),
|
||||||
name='language'),
|
path('site/<int:pk>/', views.SiteInterfaceDictionaryRetrieveView.as_view(),
|
||||||
path('site/', views.SiteInterfaceDictionaryView.as_view(),
|
|
||||||
name='site-interface'),
|
|
||||||
path('site/<int:pk>/', views.SiteInterfaceDictionaryRUDView.as_view(),
|
|
||||||
name='site-interface-rud'),
|
name='site-interface-rud'),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,14 @@ class LanguageViewMixin(generics.GenericAPIView):
|
||||||
|
|
||||||
|
|
||||||
# Views
|
# Views
|
||||||
class LanguageListCreateView(LanguageViewMixin, generics.ListCreateAPIView):
|
class LanguageListView(LanguageViewMixin, generics.ListAPIView):
|
||||||
"""List view for model Language"""
|
"""List view for model Language"""
|
||||||
|
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
|
permission_classes = (permissions.IsAuthenticatedOrReadOnly, )
|
||||||
|
|
||||||
|
|
||||||
class LanguageRetrieveUpdateDestroyView(LanguageViewMixin,
|
class LanguageRetrieveView(LanguageViewMixin, generics.RetrieveAPIView):
|
||||||
generics.RetrieveUpdateDestroyAPIView):
|
|
||||||
"""Retrieve, Update, Destroy view for model Language"""
|
"""Retrieve, Update, Destroy view for model Language"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -37,7 +36,7 @@ class SiteInterfaceDictionaryMixin:
|
||||||
|
|
||||||
class SiteInterfaceDictionaryView(JWTGenericViewMixin,
|
class SiteInterfaceDictionaryView(JWTGenericViewMixin,
|
||||||
SiteInterfaceDictionaryMixin,
|
SiteInterfaceDictionaryMixin,
|
||||||
generics.ListCreateAPIView):
|
generics.ListAPIView):
|
||||||
"""Site interface dictionary LC view."""
|
"""Site interface dictionary LC view."""
|
||||||
|
|
||||||
pagination_class = None
|
pagination_class = None
|
||||||
|
|
@ -46,9 +45,9 @@ class SiteInterfaceDictionaryView(JWTGenericViewMixin,
|
||||||
filter_fields = ['page', 'keywords']
|
filter_fields = ['page', 'keywords']
|
||||||
|
|
||||||
|
|
||||||
class SiteInterfaceDictionaryRUDView(JWTGenericViewMixin,
|
class SiteInterfaceDictionaryRetrieveView(JWTGenericViewMixin,
|
||||||
SiteInterfaceDictionaryMixin,
|
SiteInterfaceDictionaryMixin,
|
||||||
generics.RetrieveUpdateDestroyAPIView):
|
generics.RetrieveAPIView):
|
||||||
"""Site interface dictionary RUD view."""
|
"""Site interface dictionary RUD view."""
|
||||||
|
|
||||||
permission_classes = (permissions.AllowAny,)
|
permission_classes = (permissions.AllowAny,)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ from django.urls import path, include, re_path
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
from drf_yasg.views import get_schema_view
|
from drf_yasg.views import get_schema_view
|
||||||
from rest_framework import permissions
|
from rest_framework import permissions
|
||||||
|
from account.views.common import FCMDeviceViewSet
|
||||||
|
|
||||||
# Docs urls
|
# Docs urls
|
||||||
schema_view = get_schema_view(
|
schema_view = get_schema_view(
|
||||||
|
|
@ -48,22 +48,13 @@ urlpatterns_doc = [
|
||||||
name='schema-redoc'),
|
name='schema-redoc'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# API urls
|
|
||||||
urlpatterns_auth = [
|
|
||||||
path('api/auth/', include('authorization.urls.common')),
|
|
||||||
]
|
|
||||||
|
|
||||||
api_urlpatterns = [
|
api_urlpatterns = [
|
||||||
path('gallery/', include(('gallery.urls', 'gallery'),
|
path('auth/', include('authorization.urls.common')),
|
||||||
namespace='gallery')),
|
path('device/', FCMDeviceViewSet.as_view(), name='fcm-device-create'),
|
||||||
path('location/', include(('location.urls', 'location'),
|
path('web/', include(('project.urls.web', 'web'), namespace='web')),
|
||||||
namespace='location')),
|
path('back/', include(('project.urls.back', 'back'), namespace='back')),
|
||||||
path('main/', include(('main.urls', 'main'),
|
path('mobile/', include(('project.urls.mobile', 'mobile'), namespace='mobile')),
|
||||||
namespace='main')),
|
|
||||||
path('translation/', include(('translation.urls', 'translation'),
|
|
||||||
namespace='translation')),
|
|
||||||
path('web/', include(('project.urls.web', 'web'),
|
|
||||||
namespace='web')),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
@ -72,9 +63,7 @@ urlpatterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns = urlpatterns + \
|
urlpatterns = urlpatterns + \
|
||||||
urlpatterns_auth + \
|
|
||||||
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
|
||||||
urlpatterns.extend(urlpatterns_doc)
|
urlpatterns.extend(urlpatterns_doc)
|
||||||
|
|
|
||||||
14
project/urls/back.py
Normal file
14
project/urls/back.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
app_name = 'back'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('gallery/', include(('gallery.urls', 'gallery'),
|
||||||
|
namespace='gallery')),
|
||||||
|
# path('account/', include('account.urls.web')),
|
||||||
|
# path('advertisement/', include('advertisement.urls.web')),
|
||||||
|
# path('collection/', include('collection.urls.web')),
|
||||||
|
# path('establishments/', include('establishment.urls.web')),
|
||||||
|
# path('news/', include('news.urls.web')),
|
||||||
|
# path('partner/', include('partner.urls.web')),
|
||||||
|
]
|
||||||
12
project/urls/mobile.py
Normal file
12
project/urls/mobile.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
app_name = 'mobile'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
# path('account/', include('account.urls.web')),
|
||||||
|
# path('advertisement/', include('advertisement.urls.web')),
|
||||||
|
# path('collection/', include('collection.urls.web')),
|
||||||
|
# path('establishments/', include('establishment.urls.web')),
|
||||||
|
# path('news/', include('news.urls.web')),
|
||||||
|
# path('partner/', include('partner.urls.web')),
|
||||||
|
]
|
||||||
|
|
@ -24,4 +24,10 @@ urlpatterns = [
|
||||||
path('establishments/', include('establishment.urls.web')),
|
path('establishments/', include('establishment.urls.web')),
|
||||||
path('news/', include('news.urls.web')),
|
path('news/', include('news.urls.web')),
|
||||||
path('partner/', include('partner.urls.web')),
|
path('partner/', include('partner.urls.web')),
|
||||||
|
path('location/', include(('location.urls', 'location'),
|
||||||
|
namespace='location')),
|
||||||
|
path('main/', include(('main.urls', 'main'),
|
||||||
|
namespace='main')),
|
||||||
|
path('translation/', include(('translation.urls', 'translation'),
|
||||||
|
namespace='translation')),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user