added report app

This commit is contained in:
Anatoly 2020-02-05 16:46:22 +03:00
parent ab3666b03d
commit 8eb07205e1
25 changed files with 394 additions and 5 deletions

0
apps/report/__init__.py Normal file
View File

8
apps/report/admin.py Normal file
View File

@ -0,0 +1,8 @@
from django.contrib import admin
from report.models import Report
@admin.register(Report)
class ReportAdmin(admin.ModelAdmin):
"""Report admin model."""

8
apps/report/apps.py Normal file
View File

@ -0,0 +1,8 @@
from django.apps import AppConfig
from django.utils.text import gettext_lazy as _
class ReportConfig(AppConfig):
name = 'report'
verbose_name = _('Report')

26
apps/report/filters.py Normal file
View File

@ -0,0 +1,26 @@
"""Filters for application report."""
from django_filters import rest_framework as filters
from report.models import Report
class ReportFilterSet(filters.FilterSet):
"""Report filter set."""
source = filters.ChoiceFilter(
choices=Report.SOURCE_CHOICES,
help_text='Filter allow filtering QuerySet by a field - source.'
'Choices - 0 (Back office), 1 (Web), 2 (Mobile)'
)
category = filters.ChoiceFilter(
choices=Report.CATEGORY_CHOICES,
help_text='Filter allow filtering QuerySet by a field - category.'
'Choices - 0 (Bug), 1 (Suggestion/improvement)'
)
class Meta:
"""Meta class."""
model = Report
fields = (
'source',
'category',
)

View File

@ -0,0 +1,31 @@
# Generated by Django 2.2.7 on 2020-02-05 12:16
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Date created')),
('modified', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('source', models.PositiveSmallIntegerField(choices=[(0, 'Back office'), (1, 'Web'), (2, 'Mobile')], verbose_name='source')),
('category', models.PositiveSmallIntegerField(choices=[(0, 'Bug'), (1, 'Suggestion/improvement')], verbose_name='category')),
('url', models.URLField(verbose_name='URL')),
('description', models.TextField(verbose_name='description')),
],
options={
'verbose_name': 'Report',
'verbose_name_plural': 'Reports',
},
),
]

View File

84
apps/report/models.py Normal file
View File

@ -0,0 +1,84 @@
from django.conf import settings
from django.core.mail import send_mail
from django.db import models
from django.utils.text import gettext_lazy as _
from report.tasks import send_report_task
from utils.models import ProjectBaseMixin
class ReportManager(models.Manager):
"""Manager for model Report."""
def make(self, source: int, category, url: str, description: str):
"""Make object."""
obj = self.create(
source=source,
category=category,
url=url,
description=description
)
if settings.USE_CELERY:
send_report_task.delay(obj.id)
else:
send_report_task(obj.id)
return obj
class ReportQuerySet(models.QuerySet):
"""QuerySet for model Report."""
def by_source(self, source: int):
"""Return QuerySet filtered by a source."""
return self.filter(source=source)
class Report(ProjectBaseMixin):
"""Report model."""
BACK_OFFICE = 0
WEB = 1
MOBILE = 2
SOURCE_CHOICES = (
(BACK_OFFICE, _('Back office')),
(WEB, _('Web')),
(MOBILE, _('Mobile')),
)
BUG = 0
SUGGESTION_IMPROVEMENT = 1
CATEGORY_CHOICES = (
(BUG, _('Bug')),
(SUGGESTION_IMPROVEMENT, _('Suggestion/improvement')),
)
source = models.PositiveSmallIntegerField(choices=SOURCE_CHOICES,
verbose_name=_('source'))
category = models.PositiveSmallIntegerField(choices=CATEGORY_CHOICES,
verbose_name=_('category'))
url = models.URLField(verbose_name=_('URL'))
description = models.TextField(verbose_name=_('description'))
objects = ReportManager.from_queryset(ReportQuerySet)()
class Meta:
"""Meta class."""
verbose_name = _('Report')
verbose_name_plural = _('Reports')
def get_body_email_message(self):
"""Prepare the body of the email message"""
return {
'subject': self.get_category_display(),
'message': str(self.description),
'html_message': self.description,
'from_email': settings.EMAIL_HOST_USER,
'recipient_list': [settings.EMAIL_TECHNICAL_SUPPORT, ],
}
def send_email(self):
"""Send an email reset user password"""
send_mail(**self.get_body_email_message())

View File

@ -0,0 +1,34 @@
"""DRF-serializers for application report."""
from rest_framework import serializers
from report.models import Report
class ReportBaseSerializer(serializers.ModelSerializer):
"""Serializer for model Report."""
category_display = serializers.CharField(source='get_category_display', read_only=True)
class Meta:
"""Meta class."""
model = Report
fields = [
'id',
'category',
'category_display',
'url',
'description',
]
extra_kwargs = {
'source': {'required': False},
'category': {'write_only': True}
}
def validate(self, attrs):
"""An overridden validate method."""
attrs['source'] = self.context.get('view').get_source()
return attrs
def create(self, validated_data):
"""An overridden create method."""
return self.Meta.model.objects.make(**validated_data)

18
apps/report/tasks.py Normal file
View File

@ -0,0 +1,18 @@
"""Report app tasks."""
import logging
from celery import shared_task
logger = logging.getLogger(__name__)
@shared_task
def send_report_task(report_id: int):
from report.models import Report
report_qs = Report.objects.filter(id=report_id)
if report_qs.exists():
report = report_qs.first()
report.send_email()
else:
logger.error(f'Error sending report {report_id}')

1
apps/report/tests.py Normal file
View File

@ -0,0 +1 @@
# Create your tests here.

View File

10
apps/report/urls/back.py Normal file
View File

@ -0,0 +1,10 @@
"""Back office URL patterns for application report."""
from django.urls import path
from report.views import back as views
app_name = 'report'
urlpatterns = [
path('', views.ReportListCreateView.as_view(), name='report-list-create'),
]

View File

@ -0,0 +1,9 @@
"""Common URL patterns for application report."""
from django.urls import path
from report.views import common as views
app_name = 'report'
urlpatterns = [
path('<int:pk>/', views.ReportRetrieveView.as_view(), name='report-retrieve')
]

View File

@ -0,0 +1,5 @@
"""Mobile URL patterns for application report."""
app_name = 'report'
urlpatterns = []

11
apps/report/urls/web.py Normal file
View File

@ -0,0 +1,11 @@
"""Web URL patterns for application report."""
from django.urls import path
from report.urls.common import urlpatterns as common_urlpatterns
from report.views import web as views
app_name = 'report'
urlpatterns = common_urlpatterns + [
path('', views.ReportListCreateView.as_view(), name='report-list-create'),
]

View File

39
apps/report/views/back.py Normal file
View File

@ -0,0 +1,39 @@
"""Views for application report."""
from rest_framework.generics import ListCreateAPIView
from report.models import Report
from report.views.common import ReportBaseView
class ReportListCreateView(ReportBaseView, ListCreateAPIView):
"""
## View for getting list of reports or create a new one.
### POST-request data
Request data attributes:
* category: integer (0 - Bug, 1 - Suggestion improvement)
* url: char (URL)
* description: text (problem description)
I.e.:
```
{
"category": 1,
"url": "http://google.com",
"description": "Description"
}
```
### Response
*GET*
Return paginated list of reports.
*POST*
Creates a new report, and returns a serialized object.
### Description
Method that allows getting list of reports or create a new one and return serialized object.
"""
@staticmethod
def get_source():
"""Return a source for view."""
return Report.BACK_OFFICE

View File

@ -0,0 +1,58 @@
"""Common views for application report."""
from rest_framework import generics
from report.filters import ReportFilterSet
from report.models import Report
from report.serializers import ReportBaseSerializer
from utils.methods import get_permission_classes
from utils.permissions import (
IsEstablishmentManager, IsContentPageManager, IsReviewManager,
IsModerator, IsRestaurantInspector, IsArtisanInspector,
IsWineryWineInspector, IsDistilleryLiquorInspector, IsProducerFoodInspector,
IsEstablishmentAdministrator
)
class ReportBaseView(generics.GenericAPIView):
"""
## Report base view.
"""
queryset = Report.objects.all()
serializer_class = ReportBaseSerializer
filter_class = ReportFilterSet
permission_classes = get_permission_classes(
IsEstablishmentManager, IsContentPageManager, IsReviewManager,
IsModerator, IsRestaurantInspector, IsArtisanInspector,
IsWineryWineInspector, IsDistilleryLiquorInspector, IsProducerFoodInspector,
IsEstablishmentAdministrator
)
@staticmethod
def get_source():
"""Return a source for view."""
return NotImplementedError('You must implement "get_source" method')
class ReportRetrieveView(ReportBaseView, generics.RetrieveAPIView):
"""
## View for retrieving serialized instance.
### Response
Return serialized object.
I.e.:
```
{
"count": 7,
"next": null,
"previous": null,
"results": [
{
"id": 1,
...
}
]
}
```
### Description
Method that allows retrieving serialized report object.
"""

39
apps/report/views/web.py Normal file
View File

@ -0,0 +1,39 @@
"""Views for application report."""
from rest_framework.generics import ListCreateAPIView
from report.models import Report
from report.views.common import ReportBaseView
class ReportListCreateView(ReportBaseView, ListCreateAPIView):
"""
## View for getting list of reports or create a new one.
### POST-request data
Request data attributes:
* category: integer (0 - Bug, 1 - Suggestion improvement)
* url: char (URL)
* description: text (problem description)
I.e.:
```
{
"category": 1,
"url": "http://google.com",
"description": "Description"
}
```
### Response
*GET*
Return paginated list of reports.
*POST*
Creates a new report, and returns a serialized object.
### Description
Method that allows getting list of reports or create a new one and return serialized object.
"""
@staticmethod
def get_source():
"""Return a source for view."""
return Report.WEB

View File

@ -1,11 +1,13 @@
"""Translation app models.""" """Translation app models."""
from django.apps import apps
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.indexes import GinIndex from django.contrib.postgres.indexes import GinIndex
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.apps import apps
from utils.models import ProjectBaseMixin, LocaleManagerMixin from utils.models import ProjectBaseMixin, LocaleManagerMixin
class LanguageQuerySet(models.QuerySet): class LanguageQuerySet(models.QuerySet):
"""QuerySet for model Language""" """QuerySet for model Language"""
@ -55,7 +57,7 @@ class SiteInterfaceDictionaryManager(LocaleManagerMixin):
Tag = apps.get_model('tag', 'Tag') Tag = apps.get_model('tag', 'Tag')
"""Creates or updates translation for EXISTING in DB Tag""" """Creates or updates translation for EXISTING in DB Tag"""
if not tag.pk or not isinstance(tag, Tag): if not tag.pk or not isinstance(tag, Tag):
raise NotImplementedError raise NotImplementedError()
if tag.translation: if tag.translation:
tag.translation.text = translations tag.translation.text = translations
tag.translation.page = 'tag' tag.translation.page = 'tag'
@ -74,7 +76,7 @@ class SiteInterfaceDictionaryManager(LocaleManagerMixin):
"""Creates or updates translation for EXISTING in DB TagCategory""" """Creates or updates translation for EXISTING in DB TagCategory"""
TagCategory = apps.get_model('tag', 'TagCategory') TagCategory = apps.get_model('tag', 'TagCategory')
if not tag_category.pk or not isinstance(tag_category, TagCategory): if not tag_category.pk or not isinstance(tag_category, TagCategory):
raise NotImplementedError raise NotImplementedError()
if tag_category.translation: if tag_category.translation:
tag_category.translation.text = translations tag_category.translation.text = translations
tag_category.translation.page = 'tag' tag_category.translation.page = 'tag'

View File

@ -143,7 +143,7 @@ class OAuthProjectMixin:
def get_source(self): def get_source(self):
"""Method to get of platform""" """Method to get of platform"""
return NotImplementedError return NotImplementedError()
class BaseAttributes(ProjectBaseMixin): class BaseAttributes(ProjectBaseMixin):

View File

@ -76,6 +76,7 @@ PROJECT_APPS = [
'favorites.apps.FavoritesConfig', 'favorites.apps.FavoritesConfig',
'rating.apps.RatingConfig', 'rating.apps.RatingConfig',
'tag.apps.TagConfig', 'tag.apps.TagConfig',
'report.apps.ReportConfig',
] ]
EXTERNAL_APPS = [ EXTERNAL_APPS = [
@ -566,3 +567,5 @@ COUNTRY_CALLING_CODES = {
CALLING_CODES_ANTILLES_GUYANE_WEST_INDIES = [590, 594, 1758, 596] CALLING_CODES_ANTILLES_GUYANE_WEST_INDIES = [590, 594, 1758, 596]
DEFAULT_CALLING_CODE_ANTILLES_GUYANE_WEST_INDIES = 590 DEFAULT_CALLING_CODE_ANTILLES_GUYANE_WEST_INDIES = 590
EMAIL_TECHNICAL_SUPPORT = 'it-report@gaultmillau.com'

View File

@ -123,3 +123,5 @@ EMAIL_PORT = 587
# ADD TRANSFER TO INSTALLED APPS # ADD TRANSFER TO INSTALLED APPS
INSTALLED_APPS.append('transfer.apps.TransferConfig') INSTALLED_APPS.append('transfer.apps.TransferConfig')
EMAIL_TECHNICAL_SUPPORT = 'a.feteleu@spider.ru'

View File

@ -18,4 +18,5 @@ urlpatterns = [
namespace='advertisement')), namespace='advertisement')),
path('main/', include('main.urls.back')), path('main/', include('main.urls.back')),
path('partner/', include('partner.urls.back')), path('partner/', include('partner.urls.back')),
path('report/', include('report.urls.back')),
] ]

View File

@ -36,5 +36,5 @@ urlpatterns = [
path('favorites/', include('favorites.urls')), path('favorites/', include('favorites.urls')),
path('timetables/', include('timetable.urls.web')), path('timetables/', include('timetable.urls.web')),
path('products/', include('product.urls.web')), path('products/', include('product.urls.web')),
path('report/', include('report.urls.web')),
] ]