diff --git a/aitrainer_backoffice/aitrainer_backoffice/admin/__init__.py b/aitrainer_backoffice/aitrainer_backoffice/admin/__init__.py index c9fa97f..4419bf1 100644 --- a/aitrainer_backoffice/aitrainer_backoffice/admin/__init__.py +++ b/aitrainer_backoffice/aitrainer_backoffice/admin/__init__.py @@ -12,3 +12,4 @@ from .training_plan import TrainingPlanAdmin, TrainingPlanDetailAdmin from .faq import FaqAdmin from .split_tests import SplitTestAdmin from .training_plan_day import TrainingPlanDayAdmin +from .controlling import ControllingAdmin diff --git a/aitrainer_backoffice/aitrainer_backoffice/admin/base_site.html b/aitrainer_backoffice/aitrainer_backoffice/admin/base_site.html index ce0328d..176e0d8 100644 --- a/aitrainer_backoffice/aitrainer_backoffice/admin/base_site.html +++ b/aitrainer_backoffice/aitrainer_backoffice/admin/base_site.html @@ -1,6 +1,9 @@ {% extends "admin/base_site.html" %} - {% block extrahead %} {{ block.super }} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block content %} +

Controlling

+{% endblock %} diff --git a/aitrainer_backoffice/aitrainer_backoffice/admin/controlling.py b/aitrainer_backoffice/aitrainer_backoffice/admin/controlling.py new file mode 100644 index 0000000..c5b30c9 --- /dev/null +++ b/aitrainer_backoffice/aitrainer_backoffice/admin/controlling.py @@ -0,0 +1,29 @@ +from django.contrib import admin +from django.db import models +from django.urls import path +from django.http import HttpResponse + + +class Controlling(models.Model): + class Meta: + verbose_name_plural = 'Controlling' + app_label = 'aitrainer_backoffice' + + +def my_custom_view(request): + return HttpResponse('Admin Custom View') + + +class ControllingAdmin(admin.ModelAdmin): + model = Controlling + + def get_urls(self): + view_name = '{}_{}_changelist'.format( + self.model._meta.app_label, self.model._meta.model_name) + return [ + path('my_admin_path/', my_custom_view, name=view_name), + ] + + +admin.site.register(Controlling, ControllingAdmin) +admin.autodiscover() diff --git a/aitrainer_backoffice/aitrainer_backoffice/db_router.py b/aitrainer_backoffice/aitrainer_backoffice/db_router.py new file mode 100644 index 0000000..28dc4d8 --- /dev/null +++ b/aitrainer_backoffice/aitrainer_backoffice/db_router.py @@ -0,0 +1,22 @@ +class TestRouter: + """ + A router to control all database operations on models + """ + live_app_labels = {'controlling'} + + def db_for_read(self, model, **hints): + if model._meta.app_label == 'controlling': + return 'live' + else: + return 'default' + + def db_for_write(self, model, **hints): + if model._meta.app_label == 'controlling': + raise Exception("This table cannot be changed!") + return 'default' + + def allow_relation(self, obj1, obj2, **hints): + return True + + def allow_migrate(self, db, app_label, model_name=None, **hints): + return False diff --git a/aitrainer_backoffice/aitrainer_backoffice/models/__init__.py b/aitrainer_backoffice/aitrainer_backoffice/models/__init__.py index 963112a..32b3136 100644 --- a/aitrainer_backoffice/aitrainer_backoffice/models/__init__.py +++ b/aitrainer_backoffice/aitrainer_backoffice/models/__init__.py @@ -15,3 +15,4 @@ from .training_plan import TrainingPlan, TrainingPlanDetail from .faq import Faq, FaqTranslation from .split_tests import SplitTests from .training_plan_day import TrainingPlanDay, TrainingPlanDayTranslation +from .controlling import Controlling \ No newline at end of file diff --git a/aitrainer_backoffice/aitrainer_backoffice/models/controlling.py b/aitrainer_backoffice/aitrainer_backoffice/models/controlling.py new file mode 100644 index 0000000..decaa99 --- /dev/null +++ b/aitrainer_backoffice/aitrainer_backoffice/models/controlling.py @@ -0,0 +1,9 @@ +from django.db import models + + +class Controlling(models.Model): + sort = models.IntegerField(blank=True) + + class Meta: + verbose_name_plural = 'Controlling' + app_label = 'controlling' diff --git a/aitrainer_backoffice/aitrainer_backoffice/models/multi_db.py b/aitrainer_backoffice/aitrainer_backoffice/models/multi_db.py new file mode 100644 index 0000000..16b14b7 --- /dev/null +++ b/aitrainer_backoffice/aitrainer_backoffice/models/multi_db.py @@ -0,0 +1,28 @@ +from django.contrib import admin + + +class MultiDBModelAdmin(admin.ModelAdmin): + # A handy constant for the name of the alternate database. + using = 'default' + + def save_model(self, request, obj, form, change): + # Tell Django to save objects to the 'other' database. + obj.save(using=self.using) + + def delete_model(self, request, obj): + # Tell Django to delete objects from the 'other' database + obj.delete(using=self.using) + + def get_queryset(self, request): + # Tell Django to look for objects on the 'other' database. + return super().get_queryset(request).using(self.using) + + def formfield_for_foreignkey(self, db_field, request, **kwargs): + # Tell Django to populate ForeignKey widgets using a query + # on the 'other' database. + return super().formfield_for_foreignkey(db_field, request, using=self.using, **kwargs) + + def formfield_for_manytomany(self, db_field, request, **kwargs): + # Tell Django to populate ManyToMany widgets using a query + # on the 'other' database. + return super().formfield_for_manytomany(db_field, request, using=self.using, **kwargs) \ No newline at end of file diff --git a/aitrainer_backoffice/aitrainer_backoffice/settings/dev.py b/aitrainer_backoffice/aitrainer_backoffice/settings/dev.py index 4e64134..2c69017 100644 --- a/aitrainer_backoffice/aitrainer_backoffice/settings/dev.py +++ b/aitrainer_backoffice/aitrainer_backoffice/settings/dev.py @@ -12,7 +12,7 @@ https://docs.djangoproject.com/en/3.0/ref/settings/ import os -BACKOFFICE_VERSION = 1.22 +BACKOFFICE_VERSION = 1.23 # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -38,6 +38,7 @@ ALLOWED_HOSTS = ['localhost'] INSTALLED_APPS = [ 'aitrainer_backoffice', + 'controlling.apps.ControllingConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -47,6 +48,8 @@ INSTALLED_APPS = [ 'ckeditor', 'ckeditor_uploader', 'django_admin_json_editor', + 'rangefilter', + 'django_crontab' ] MIDDLEWARE = [ @@ -93,7 +96,7 @@ DATABASES = { }, 'live': { 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'aitrainer2', + 'NAME': 'aitrainer', 'USER': 'aitrainer', 'PASSWORD': 'andio2009', 'HOST': '127.0.0.1', @@ -101,6 +104,8 @@ DATABASES = { } } +DATABASE_ROUTERS = ['aitrainer_backoffice.db_router.TestRouter'] + # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators @@ -166,3 +171,7 @@ LOGGING = { }, }, } + +CRONJOBS = [ + ('4 */1 * * *', 'controlling.cron.sync_customers') +] diff --git a/aitrainer_backoffice/aitrainer_backoffice/settings/prod.py b/aitrainer_backoffice/aitrainer_backoffice/settings/prod.py index 00d60ea..f7b49f9 100644 --- a/aitrainer_backoffice/aitrainer_backoffice/settings/prod.py +++ b/aitrainer_backoffice/aitrainer_backoffice/settings/prod.py @@ -12,7 +12,7 @@ https://docs.djangoproject.com/en/3.0/ref/settings/ import os -BACKOFFICE_VERSION = 1.22 +BACKOFFICE_VERSION = 1.23 # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -34,6 +34,7 @@ ALLOWED_HOSTS = ['62.171.188.119', 'localhost', 'andio.eu', 'aitrainer.info','ai INSTALLED_APPS = [ 'aitrainer_backoffice.aitrainer_backoffice', + 'controlling.apps.ControllingConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -43,6 +44,8 @@ INSTALLED_APPS = [ 'ckeditor', 'ckeditor_uploader', 'django_admin_json_editor', + 'rangefilter', + 'django_crontab' ] MIDDLEWARE = [ @@ -89,6 +92,8 @@ DATABASES = { } } +DATABASE_ROUTERS = ['aitrainer_backoffice.db_router.TestRouter'] + # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators @@ -170,3 +175,7 @@ CACHES = { } } +CRONJOBS = [ + ('4 */1 * * *', 'controlling.cron.sync_customers') +] + diff --git a/aitrainer_backoffice/aitrainer_backoffice/templates/controlling/mautic.html b/aitrainer_backoffice/aitrainer_backoffice/templates/controlling/mautic.html new file mode 100644 index 0000000..4a7e824 --- /dev/null +++ b/aitrainer_backoffice/aitrainer_backoffice/templates/controlling/mautic.html @@ -0,0 +1,12 @@ +{% extends 'admin/change_list.html' %} + +{% block object-tools %} +
+
+ {% csrf_token %} + +
+
+
+ {{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/aitrainer_backoffice/controlling/__init__.py b/aitrainer_backoffice/controlling/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aitrainer_backoffice/controlling/admin/__init__.py b/aitrainer_backoffice/controlling/admin/__init__.py new file mode 100644 index 0000000..18b00eb --- /dev/null +++ b/aitrainer_backoffice/controlling/admin/__init__.py @@ -0,0 +1,3 @@ +from .customer import CustomerAdmin +from .frequent_customers import FrequentCustomersAdmin +from .frequent_exercises import FrequentExerciseTypeAdmin diff --git a/aitrainer_backoffice/controlling/admin/customer.py b/aitrainer_backoffice/controlling/admin/customer.py new file mode 100644 index 0000000..992165e --- /dev/null +++ b/aitrainer_backoffice/controlling/admin/customer.py @@ -0,0 +1,83 @@ +from abc import ABC +from datetime import datetime + +from django.contrib.admin import SimpleListFilter +from django.http import HttpResponseRedirect +from django.utils.translation import ugettext_lazy as _ +from django.contrib import admin +from rangefilter.filters import DateRangeFilter, DateTimeRangeFilter +from django.urls import path + +from ..models.customer import Customer +from ..models.customer import Sport +from ..mautic import MauticHelper +from ..cron import cron + + +class SportFilter(SimpleListFilter, ABC): + title = "Sport" + parameter_name = 'sport_id' + + def lookups(self, request, model_admin): + data = [] + for s in Sport.objects.filter(): + data.append([s.sport_id, s.sport_name]) + return data + + def queryset(self, request, queryset): + if self.value(): + return Customer.objects.filter(sport__sport_id=self.value()) + else: + return queryset + + +class CustomerAdmin(admin.ModelAdmin): + change_list_template = "controlling/mautic.html" + list_display = ('customer_id', 'name','firstname', 'email', 'date_add', 'get_sport') + list_filter = ( + ('date_add', DateRangeFilter), + SportFilter + ) + + def get_sport(self, obj): + return obj.sport.sport_name + + get_sport.short_description = 'Sport' + get_sport.admin_order_field = 'sport__sport_name' + + + # If you would like to add a default range filter + # method pattern "get_rangefilter_{field_name}_default" + def get_rangefilter_date_add_default(self, request): + return (datetime.today(), datetime.today()) + + # If you would like to change a title range filter + # method pattern "get_rangefilter_{field_name}_title" + def get_rangefilter_date_add_title(self, request, field_path): + return _('Registered') + + def changelist_view(self, request, extra_context=None): + if not request.GET.__contains__('date_add__range__gte'): + q = request.GET.copy() + q['date_add__range__gte'] = datetime.today() + q['date_add__range__lte'] = datetime.today() + request.GET = q + request.META['QUERY_STRING'] = request.GET.urlencode() + return super(CustomerAdmin, self).changelist_view( + request, extra_context=extra_context) + + def get_urls(self): + urls = super().get_urls() + my_urls = [ + path('mautic/', self.set_mautic), + ] + return my_urls + urls + + def set_mautic(self, request): + cron.sync_customers() + self.message_user(request, "All heroes are now synced") + return HttpResponseRedirect("../") + + +admin.site.register(Customer, CustomerAdmin) +admin.autodiscover() diff --git a/aitrainer_backoffice/controlling/admin/frequent_customers.py b/aitrainer_backoffice/controlling/admin/frequent_customers.py new file mode 100644 index 0000000..51e7e60 --- /dev/null +++ b/aitrainer_backoffice/controlling/admin/frequent_customers.py @@ -0,0 +1,24 @@ +from django.contrib import admin + +from ..models.frequent_customers import FrequentCustomers + + + +class FrequentCustomersAdmin(admin.ModelAdmin): + list_display = ('customer_id', 'name', 'firstname', 'email', 'exercise_count') + + #def get_queryset(self, request): + # qs = super(FrequentCustomersAdmin, self).get_queryset(request) + # return FrequentCustomers.objects.extra( + # select=["customer_id, name, firstname, email, ( select count(exercise_id) from exercises where exercises.customer_id = customer_customer.id) as exercise_count "], + #group_by=["customer_id"], + #having=["exercise_count > 10"], + # order_by=["-exercise_count"] + #) + #return qs.values("exercises").annotate(exercise_count=Count('exercises')).order_by('-exercise_count') + #return Exercises.objects.annotate(count=Count("customer__customer_id")) + #return qs.values('user').annotate(visit_sum=Count('visit_count')).order_by('-visit_sum') + + +admin.site.register(FrequentCustomers, FrequentCustomersAdmin) +admin.autodiscover() diff --git a/aitrainer_backoffice/controlling/admin/frequent_exercises.py b/aitrainer_backoffice/controlling/admin/frequent_exercises.py new file mode 100644 index 0000000..67d95bc --- /dev/null +++ b/aitrainer_backoffice/controlling/admin/frequent_exercises.py @@ -0,0 +1,15 @@ +from django.contrib import admin + +from ..models import ExerciseType + + +class FrequentExerciseTypeAdmin(admin.ModelAdmin): + list_display = ('exercise_type_id', 'name', 'exercise_count') + + def get_queryset(self, request): + qs = super(FrequentExerciseTypeAdmin, self).get_queryset(request) + return qs.filter(active=True) + + +admin.site.register(ExerciseType, FrequentExerciseTypeAdmin) +admin.autodiscover() diff --git a/aitrainer_backoffice/controlling/apps.py b/aitrainer_backoffice/controlling/apps.py new file mode 100644 index 0000000..50e6d1e --- /dev/null +++ b/aitrainer_backoffice/controlling/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ControllingConfig(AppConfig): + app_label = 'controlling' + name = 'controlling' diff --git a/aitrainer_backoffice/controlling/cron/__init__.py b/aitrainer_backoffice/controlling/cron/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aitrainer_backoffice/controlling/cron/cron.py b/aitrainer_backoffice/controlling/cron/cron.py new file mode 100644 index 0000000..b43f190 --- /dev/null +++ b/aitrainer_backoffice/controlling/cron/cron.py @@ -0,0 +1,6 @@ +from ..mautic import MauticHelper + + +def sync_customers(): + helper = MauticHelper() + helper.sync() \ No newline at end of file diff --git a/aitrainer_backoffice/controlling/mautic/__init__.py b/aitrainer_backoffice/controlling/mautic/__init__.py new file mode 100644 index 0000000..4f7a733 --- /dev/null +++ b/aitrainer_backoffice/controlling/mautic/__init__.py @@ -0,0 +1 @@ +from .helper import MauticHelper diff --git a/aitrainer_backoffice/controlling/mautic/helper.py b/aitrainer_backoffice/controlling/mautic/helper.py new file mode 100644 index 0000000..4230ec0 --- /dev/null +++ b/aitrainer_backoffice/controlling/mautic/helper.py @@ -0,0 +1,71 @@ +import requests +import logging +from django.db import connections + +from ..models.customer import Customer + + +class MauticHelper: + + + def sync(self): + logger = logging.getLogger(__name__) + logger.info("Syncronising...") + + last_synced_date = self.get_last_synced_date() + + if len(last_synced_date) != 0: + qs = Customer.objects.raw( + 'SELECT * from customer WHERE date_add > "' + last_synced_date + '" and synced_date is null') + else: + qs = Customer.objects.raw( + 'SELECT * from customer WHERE synced_date is null') + + headers = { + 'content-type': "application/x-www-form-urlencoded", + 'cache-control': "no-cache" + } + index = 0 + for customer in qs: + goal = customer.goal if customer.goal is not None else "" + fitness_level = customer.fitness_level if customer.fitness_level is not None else "" + data = "mauticform[email]=" + customer.email + \ + "&mauticform[f_name]=" + customer.name + \ + "&mauticform[firstname]=" + customer.firstname + \ + "&mauticform[goal]=" + goal + \ + "&mauticform[fitness_level]=" + fitness_level + \ + "&mauticform[subscribed]=" + str(customer.date_add) + \ + "&mauticform[database_id]=" + str(customer.customer_id) + \ + "&mauticform[formId]=1" + \ + "&mauticform[formName]=appsync" + + print(data) + + form_url = 'https://mautic.aitrainer.app/form/submit?formId=1' + response = requests.post(form_url, data=data.encode('utf-8'), headers=headers) + print(str(response.status_code)) + + if response.status_code == 200: + with connections["live"].cursor() as cursor: + cursor.execute("UPDATE customer SET synced_date = NOW() WHERE customer_id=" + + str(customer.customer_id)) + #if index == 0: + # break + index = index + 1 + + logger.info("Syncronised customer count: " + str(index)) + + return True + + def get_last_synced_date(self): + qs = Customer.objects.raw('SELECT customer_id, max(synced_date) as synced_date from customer') + for c in qs: + if c.synced_date is None: + return "" + synced_date = c.synced_date.strftime('%Y-%m-%d') + print(synced_date) + return synced_date + + def sync_frequent_users(self): + print("SYNC FREQ USERS") + return True diff --git a/aitrainer_backoffice/controlling/models/__init__.py b/aitrainer_backoffice/controlling/models/__init__.py new file mode 100644 index 0000000..f681e50 --- /dev/null +++ b/aitrainer_backoffice/controlling/models/__init__.py @@ -0,0 +1,4 @@ +from .customer import Customer +from .exercises import Exercises +from .exercise_type import ExerciseType +from .frequent_customers import FrequentCustomers diff --git a/aitrainer_backoffice/controlling/models/customer.py b/aitrainer_backoffice/controlling/models/customer.py new file mode 100644 index 0000000..abf5334 --- /dev/null +++ b/aitrainer_backoffice/controlling/models/customer.py @@ -0,0 +1,38 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class Sport(models.Model): + sport_id = models.AutoField(primary_key = True) + language_code = models.CharField(max_length=2,default="hu") + sport_name = models.CharField(max_length=100) + + class Meta: + db_table = 'sport_translation' + + +class Customer(models.Model): + customer_id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=100, help_text='Last name', verbose_name=_("name")) + firstname = models.CharField(max_length=100, help_text='First name', verbose_name=_("firstname")) + email = models.CharField(max_length=100) + sport = models.ForeignKey(Sport, on_delete=models.CASCADE) + goal = models.CharField(max_length=20) + fitness_level = models.CharField(max_length=20) + date_add = models.DateField() + synced_date = models.DateTimeField(blank=True,null=True) + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + class Meta: + db_table = 'customer' + verbose_name = _("Customer") + verbose_name_plural = _("Customers") + app_label = 'controlling' + + def __str__(self): + return self.name diff --git a/aitrainer_backoffice/controlling/models/exercise_type.py b/aitrainer_backoffice/controlling/models/exercise_type.py new file mode 100644 index 0000000..2958a32 --- /dev/null +++ b/aitrainer_backoffice/controlling/models/exercise_type.py @@ -0,0 +1,28 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from ..models.exercises import Exercises + + +class ExerciseType(models.Model): + exercise_type_id = models.AutoField(primary_key=True) + name = models.CharField(max_length=50) + active = models.BooleanField() + + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + @property + def exercise_count(self): + count = Exercises.objects.filter(exercise_type_id=self.exercise_type_id).count() + return count + + class Meta: + db_table = 'exercise_type' + verbose_name = _("Frequent Exercise") + verbose_name_plural = _("Frequent Exercises") + diff --git a/aitrainer_backoffice/controlling/models/exercises.py b/aitrainer_backoffice/controlling/models/exercises.py new file mode 100644 index 0000000..495ac04 --- /dev/null +++ b/aitrainer_backoffice/controlling/models/exercises.py @@ -0,0 +1,25 @@ +from django.db import models + +#from ..models.exercise_type import ExerciseType +from ..models.customer import Customer + + +class Exercises(models.Model): + exercise_id = models.BigAutoField(primary_key=True) + customer = models.ForeignKey(Customer, on_delete=models.CASCADE) + exercise_type_id = models.IntegerField() + #exercise_type = models.ForeignKey(ExerciseType, on_delete=models.CASCADE) + date_add = models.DateField() + quantity = models.DecimalField(decimal_places=0, max_digits=6) + unit = models.CharField(max_length=20) + unit_quantity = models.DecimalField(decimal_places=0, max_digits=6,blank=True, null=True) + + + def has_add_permission(self, request): + return False + + def has_delete_permission(self, request, obj=None): + return False + + class Meta: + db_table = 'exercises' diff --git a/aitrainer_backoffice/controlling/models/frequent_customers.py b/aitrainer_backoffice/controlling/models/frequent_customers.py new file mode 100644 index 0000000..0bdf9cc --- /dev/null +++ b/aitrainer_backoffice/controlling/models/frequent_customers.py @@ -0,0 +1,32 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from .exercises import Exercises + + +class FrequentCustomers(models.Model): + customer_id = models.BigAutoField(primary_key=True) + name = models.CharField(max_length=100, help_text='Last name', verbose_name=_("name")) + firstname = models.CharField(max_length=100, help_text='First name', verbose_name=_("firstname")) + email = models.CharField(max_length=100) + date_add = models.DateField() + #exercises = models.ManyToManyField(Exercises) + + #def exercises(self): + # exercises = Exercises.objects.filter(customer__customer_id=self.customer_id) + # return exercises + + @property + def exercise_count(self): + count = Exercises.objects.filter(customer__customer_id=self.customer_id).count() + return count + + class Meta: + db_table = 'customer' + verbose_name = _("Frequent Customer") + verbose_name_plural = _("Frequent Customers") + #ordering = ['exercise_count'] + #filter(exercise_count__gt=10) + + def __str__(self): + return self.name diff --git a/aitrainer_backoffice/controlling/views.py b/aitrainer_backoffice/controlling/views.py new file mode 100644 index 0000000..e69de29