diff --git a/Templates/identify/register.html b/Templates/identify/register.html index ebdca10..0dc0d2a 100644 --- a/Templates/identify/register.html +++ b/Templates/identify/register.html @@ -101,6 +101,51 @@

{% trans "Create Account" %}

{% endfor %} +
+ + {% for error in form.gender.errors %} + {{ error }} + {% endfor %} +
+ +
+ + {% for error in form.age_range.errors %} + {{ error }} + {% endfor %} +
+ +
+ + {% for error in form.province.errors %} + {{ error }} + {% endfor %} +
+ +
+ + {% for error in form.municipality.errors %} + {{ error }} + {% endfor %} +
+
diff --git a/users/templates/users/parts/dashboard_cross_tab.html b/users/templates/users/parts/dashboard_cross_tab.html new file mode 100644 index 0000000..55ec2ef --- /dev/null +++ b/users/templates/users/parts/dashboard_cross_tab.html @@ -0,0 +1,97 @@ +{% extends 'users/types/user_admin.html' %} +{% load i18n %} + +{% block parts_content %} +
+ {% include 'users/parts/filter_form.html' %} + + + +
+
+
+
+
{% trans 'Views by Gender and Age Range' %}
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
{% trans 'Cross-tabulation Data' %}
+
+
+
+ + + + + {% for header in cross_table_headers %} + + {% endfor %} + + + + {% for row in cross_table_rows %} + + {% for cell in row %} + + {% endfor %} + + {% endfor %} + +
{% trans 'Gender' %}{{ header }}
{{ cell }}
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/users/templates/users/parts/dashboard_demographics.html b/users/templates/users/parts/dashboard_demographics.html new file mode 100644 index 0000000..f6bca3c --- /dev/null +++ b/users/templates/users/parts/dashboard_demographics.html @@ -0,0 +1,122 @@ +{% extends 'users/types/user_admin.html' %} +{% load i18n %} + +{% block parts_content %} +
+ {% include 'users/parts/filter_form.html' %} + + + +
+
+
+
+
{% trans 'Views by Gender' %}
+
+
+
+ +
+
+
+
+ +
+
+
+
{% trans 'Views by Age Range' %}
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
{% trans 'Views by Province (Top 10)' %}
+
+
+
+ +
+
+
+
+ +
+
+
+
{% trans 'Gender x Age Range' %}
+
+
+
+ +
+
+
+
+
+
+ + +{% endblock %} diff --git a/users/templates/users/parts/dashboard_overview.html b/users/templates/users/parts/dashboard_overview.html index 5148afd..237ce03 100644 --- a/users/templates/users/parts/dashboard_overview.html +++ b/users/templates/users/parts/dashboard_overview.html @@ -6,7 +6,7 @@
Views per Content
- Export CSV diff --git a/users/templates/users/parts/dashboard_views_by_age.html b/users/templates/users/parts/dashboard_views_by_age.html new file mode 100644 index 0000000..2cda0d3 --- /dev/null +++ b/users/templates/users/parts/dashboard_views_by_age.html @@ -0,0 +1,81 @@ +{% extends 'users/types/user_admin.html' %} +{% load i18n %} + +{% block parts_content %} +
+ {% include 'users/parts/filter_form.html' %} + + + +
+
+
+
+
{% trans 'Views by Age Range' %}
+
+
+
+ +
+
+
+
+
+
+
+
{% trans 'Age Range Data' %}
+
+
+
+ + + + + + + + + {% for label, value in age_table %} + + + + + {% endfor %} + +
{% trans 'Age Range' %}{% trans 'Views' %}
{{ label }}{{ value }}
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/users/templates/users/parts/dashboard_views_by_gender.html b/users/templates/users/parts/dashboard_views_by_gender.html new file mode 100644 index 0000000..b2a1a51 --- /dev/null +++ b/users/templates/users/parts/dashboard_views_by_gender.html @@ -0,0 +1,81 @@ +{% extends 'users/types/user_admin.html' %} +{% load i18n %} + +{% block parts_content %} +
+ {% include 'users/parts/filter_form.html' %} + + + +
+
+
+
+
{% trans 'Views by Gender' %}
+
+
+
+ +
+
+
+
+
+
+
+
{% trans 'Gender Data' %}
+
+
+
+ + + + + + + + + {% for label, value in gender_table %} + + + + + {% endfor %} + +
{% trans 'Gender' %}{% trans 'Views' %}
{{ label }}{{ value }}
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/users/templates/users/parts/dashboard_views_by_province.html b/users/templates/users/parts/dashboard_views_by_province.html new file mode 100644 index 0000000..da78f0b --- /dev/null +++ b/users/templates/users/parts/dashboard_views_by_province.html @@ -0,0 +1,81 @@ +{% extends 'users/types/user_admin.html' %} +{% load i18n %} + +{% block parts_content %} +
+ {% include 'users/parts/filter_form.html' %} + + + +
+
+
+
+
{% trans 'Views by Province (Top 10)' %}
+
+
+
+ +
+
+
+
+
+
+
+
{% trans 'Province Data' %}
+
+
+
+ + + + + + + + + {% for label, value in province_table %} + + + + + {% endfor %} + +
{% trans 'Province' %}{% trans 'Views' %}
{{ label }}{{ value }}
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/users/templates/users/profile/user_profile.html b/users/templates/users/profile/user_profile.html index 2506275..93a8638 100644 --- a/users/templates/users/profile/user_profile.html +++ b/users/templates/users/profile/user_profile.html @@ -48,11 +48,71 @@
- - {{ form.location }} + + {{ form.gender }} +
+ +
+ + {{ form.age_range }} +
+ +
+ + +
+ +
+ +
+ + {% endblock %} \ No newline at end of file diff --git a/users/templates/users/types/user_admin.html b/users/templates/users/types/user_admin.html index 5774e99..0463fd8 100644 --- a/users/templates/users/types/user_admin.html +++ b/users/templates/users/types/user_admin.html @@ -23,6 +23,10 @@ {% trans 'Directors' %} + diff --git a/users/templates/users/types/user_client.html b/users/templates/users/types/user_client.html index e4e2aa9..11ddd18 100644 --- a/users/templates/users/types/user_client.html +++ b/users/templates/users/types/user_client.html @@ -28,13 +28,35 @@

{{ user.last_login|date:"d/m/Y" }}

+
+
{% trans "Gender" %}
+

+ {% if user.gender %} + {{ user.get_gender_display }} + {% else %} + {% trans "Not specified" %} + {% endif %} +

+
+ +
+
{% trans "Age range" %}
+

+ {% if user.age_range %} + {{ user.get_age_range_display }} + {% else %} + {% trans "Not specified" %} + {% endif %} +

+
+
{% trans "Location" %}

- {% if user.location %} - {{ user.location }} + {% if user.municipality %} + {{ user.municipality.name }} ({{ user.municipality.province.name }}) {% else %} - {% trans "Add your location" %} + {% trans "Not specified" %} {% endif %}

diff --git a/users/urls.py b/users/urls.py index d435512..5d84b71 100644 --- a/users/urls.py +++ b/users/urls.py @@ -24,4 +24,9 @@ path('admin/dashboard/genres/', views.admin_dashboard_genres, name='admin_dashboard_genres'), path('admin/dashboard/age-ratings/', views.admin_dashboard_age_ratings, name='admin_dashboard_age_ratings'), path('admin/dashboard/directors/', views.admin_dashboard_directors, name='admin_dashboard_directors'), + path('admin/dashboard/demographics/', views.admin_dashboard_demographics, name='admin_dashboard_demographics'), + path('admin/dashboard/demographics/gender/', views.admin_dashboard_views_by_gender, name='admin_dashboard_views_by_gender'), + path('admin/dashboard/demographics/age/', views.admin_dashboard_views_by_age, name='admin_dashboard_views_by_age'), + path('admin/dashboard/demographics/province/', views.admin_dashboard_views_by_province, name='admin_dashboard_views_by_province'), + path('admin/dashboard/demographics/cross-tab/', views.admin_dashboard_cross_tab, name='admin_dashboard_cross_tab'), ] \ No newline at end of file diff --git a/users/views.py b/users/views.py index 849f821..551bfaf 100644 --- a/users/views.py +++ b/users/views.py @@ -22,13 +22,17 @@ _ = gettext.gettext from web_app.forms import CustomUserChangeForm, CustomUserAdminCreationForm -from web_app.models import CustomUser, Movie, Series, UserProfile, API, Visualitzacio, UserType, RoleAuditLog +from web_app.models import CustomUser, Movie, Series, UserProfile, API, Visualitzacio, UserType, RoleAuditLog, Province, Municipality from .services import ( get_content_analytics, format_analytics_for_csv, get_genre_distribution, get_age_rating_distribution, get_director_top_list, + get_views_by_gender, + get_views_by_age_range, + get_views_by_province, + get_views_by_gender_age, PII_COLUMNS, validate_gdpr_compliance, ) @@ -81,7 +85,16 @@ def user_profile(request): else: form = CustomUserChangeForm(instance=request.user) - return render(request, 'users/profile/user_profile.html', {'form': form}) + current_province_id = None + if request.user.municipality: + current_province_id = request.user.municipality.province_id + + return render(request, 'users/profile/user_profile.html', { + 'form': form, + 'provinces': Province.objects.all(), + 'current_province_id': current_province_id, + 'current_municipality_id': request.user.municipality_id, + }) @login_required(login_url='login') @user_passes_test(is_consumer) @@ -277,14 +290,17 @@ def crear_usuario_admin(request): def export_analytics_csv(request): start_date = request.GET.get('start') end_date = request.GET.get('end') - report_type = request.GET.get('type', 'internal') + report_type = request.GET.get('type', 'internal') + + if start_date == 'None': start_date = None + if end_date == 'None': end_date = None platform_ids = get_active_platform_ids(request.user) raw_data = get_content_analytics( start_date=start_date, end_date=end_date, - plataform_id=platform_ids # Ahora pasamos una lista de IDs segura + platform_ids=platform_ids ) is_b2b = (report_type == 'b2b') @@ -386,6 +402,110 @@ def admin_dashboard_directors(request): 'platforms': API.objects.all() }) +@login_required(login_url='login') +@user_passes_test(is_admin_or_staff) +def admin_dashboard_demographics(request): + start_date = request.GET.get('start') + end_date = request.GET.get('end') + platform_ids = get_active_platform_ids(request.user) + + gender_stats = get_views_by_gender(start_date, end_date, platform_ids) + age_stats = get_views_by_age_range(start_date, end_date, platform_ids) + province_stats = get_views_by_province(start_date, end_date, platform_ids) + cross_stats = get_views_by_gender_age(start_date, end_date, platform_ids) + + gender_table = list(zip(gender_stats['labels'], gender_stats['values'])) + age_table = list(zip(age_stats['labels'], age_stats['values'])) + province_table = list(zip(province_stats['labels'], province_stats['values'])) + + return render(request, 'users/parts/dashboard_demographics.html', { + 'gender_stats': gender_stats, + 'age_stats': age_stats, + 'province_stats': province_stats, + 'cross_stats': cross_stats, + 'gender_table': gender_table, + 'age_table': age_table, + 'province_table': province_table, + 'filters': {'start': start_date, 'end': end_date}, + 'platforms': API.objects.all(), + }) + +@login_required(login_url='login') +@user_passes_test(is_admin_or_staff) +def admin_dashboard_views_by_gender(request): + start_date = request.GET.get('start') + end_date = request.GET.get('end') + platform_ids = get_active_platform_ids(request.user) + + gender_stats = get_views_by_gender(start_date, end_date, platform_ids) + gender_table = list(zip(gender_stats['labels'], gender_stats['values'])) + + return render(request, 'users/parts/dashboard_views_by_gender.html', { + 'gender_stats': gender_stats, + 'gender_table': gender_table, + 'filters': {'start': start_date, 'end': end_date}, + 'platforms': API.objects.all(), + }) + +@login_required(login_url='login') +@user_passes_test(is_admin_or_staff) +def admin_dashboard_views_by_age(request): + start_date = request.GET.get('start') + end_date = request.GET.get('end') + platform_ids = get_active_platform_ids(request.user) + + age_stats = get_views_by_age_range(start_date, end_date, platform_ids) + age_table = list(zip(age_stats['labels'], age_stats['values'])) + + return render(request, 'users/parts/dashboard_views_by_age.html', { + 'age_stats': age_stats, + 'age_table': age_table, + 'filters': {'start': start_date, 'end': end_date}, + 'platforms': API.objects.all(), + }) + +@login_required(login_url='login') +@user_passes_test(is_admin_or_staff) +def admin_dashboard_views_by_province(request): + start_date = request.GET.get('start') + end_date = request.GET.get('end') + platform_ids = get_active_platform_ids(request.user) + + province_stats = get_views_by_province(start_date, end_date, platform_ids) + province_table = list(zip(province_stats['labels'], province_stats['values'])) + + return render(request, 'users/parts/dashboard_views_by_province.html', { + 'province_stats': province_stats, + 'province_table': province_table, + 'filters': {'start': start_date, 'end': end_date}, + 'platforms': API.objects.all(), + }) + +@login_required(login_url='login') +@user_passes_test(is_admin_or_staff) +def admin_dashboard_cross_tab(request): + start_date = request.GET.get('start') + end_date = request.GET.get('end') + platform_ids = get_active_platform_ids(request.user) + + cross_stats = get_views_by_gender_age(start_date, end_date, platform_ids) + + cross_table_headers = [d['label'] for d in cross_stats['datasets']] + cross_table_rows = [] + for i, label in enumerate(cross_stats['labels']): + row = [label] + for dataset in cross_stats['datasets']: + row.append(dataset['data'][i] if i < len(dataset['data']) else 0) + cross_table_rows.append(row) + + return render(request, 'users/parts/dashboard_cross_tab.html', { + 'cross_stats': cross_stats, + 'cross_table_headers': cross_table_headers, + 'cross_table_rows': cross_table_rows, + 'filters': {'start': start_date, 'end': end_date}, + 'platforms': API.objects.all(), + }) + @user_passes_test(lambda u: u.is_superuser) def update_user_role(request): if request.method == 'POST': diff --git a/web_app/admin.py b/web_app/admin.py index 9203990..4d6c700 100644 --- a/web_app/admin.py +++ b/web_app/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.utils.safestring import mark_safe -from .models import SyncLog, API, Contingut, Movie, Series, Director, Genre, AgeRating, CustomUser, UserProfile, Valoracio, Preferits, RoleAuditLog +from .models import SyncLog, API, Contingut, Movie, Series, Director, Genre, AgeRating, CustomUser, UserProfile, Valoracio, Preferits, RoleAuditLog, Province, Municipality @admin.register(API) @@ -136,4 +136,15 @@ class RoleAuditLogAdmin(admin.ModelAdmin): list_display = ('timestamp', 'admin', 'affected_user', 'old_role', 'new_role') list_filter = ('timestamp', 'old_role', 'new_role') search_fields = ('admin__username', 'affected_user__username') - readonly_fields = ('admin', 'affected_user', 'old_role', 'new_role', 'timestamp') \ No newline at end of file + readonly_fields = ('admin', 'affected_user', 'old_role', 'new_role', 'timestamp') + +@admin.register(Province) +class ProvinceAdmin(admin.ModelAdmin): + list_display = ('ine_code', 'name') + search_fields = ('name',) + +@admin.register(Municipality) +class MunicipalityAdmin(admin.ModelAdmin): + list_display = ('ine_code', 'name', 'province') + list_filter = ('province',) + search_fields = ('name',) \ No newline at end of file diff --git a/web_app/forms.py b/web_app/forms.py index 5a49b38..a2f4bbb 100644 --- a/web_app/forms.py +++ b/web_app/forms.py @@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError from django.core.validators import MaxLengthValidator -from .models import CustomUser, API, UserType +from .models import CustomUser, API, UserType, Gender, AgeRange, Province, Municipality class CustomUserCreationForm(forms.ModelForm): """ @@ -45,9 +45,29 @@ class CustomUserCreationForm(forms.ModelForm): label=_("Select your platforms") ) + gender = forms.ChoiceField( + choices=Gender.choices, + label=_("Gender"), + required=True, + ) + + age_range = forms.ChoiceField( + choices=AgeRange.choices, + label=_("Age Range"), + required=True, + ) + + province = forms.ModelChoiceField( + queryset=Province.objects.all(), + label=_("Province"), + required=True, + ) + + class Meta: model = CustomUser - fields = ('username', 'email', 'password', 'password2', 'platforms') + fields = ('username', 'email', 'password', 'password2', 'platforms', + 'gender', 'age_range', 'province', 'municipality') def clean_password2(self): """ @@ -59,20 +79,72 @@ def clean_password2(self): if password is not None and password != password2: raise forms.ValidationError("Las contraseñas no coinciden") return password2 + + def clean(self): + cleaned_data = super().clean() + province = cleaned_data.get('province') + municipality_id = self.data.get('municipality') + + if province and municipality_id: + try: + municipality = Municipality.objects.get(id=municipality_id, province=province) + cleaned_data['municipality'] = municipality + except Municipality.DoesNotExist: + self.add_error('municipality', _("The selected municipality does not belong to the selected province.")) + elif province: + self.add_error('municipality', _("You must select a municipality.")) + + return cleaned_data + def save(self, commit=True): user = super().save(commit=False) user.set_password(self.cleaned_data['password']) + user.gender = self.cleaned_data.get('gender') + user.age_range = self.cleaned_data.get('age_range') + user.municipality = self.cleaned_data.get('municipality') + if commit: user.save() return user class CustomUserChangeForm(UserChangeForm): + province = forms.ModelChoiceField( + queryset=Province.objects.all(), + label=_("Province"), + required=False, + ) + class Meta: model = CustomUser - fields = ['username', 'first_name', 'last_name', 'email', 'avatar', 'bio', 'location', 'type'] + fields = [ + 'username', 'first_name', 'last_name', 'email', 'avatar', 'bio', + 'type', 'gender', 'age_range', 'municipality', + ] help_texts = {field: '' for field in fields} + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance and self.instance.municipality: + self.fields['province'].initial = self.instance.municipality.province + self.fields['province'].widget.attrs['data-current-province'] = self.instance.municipality.province_id + + def clean(self): + cleaned_data = super().clean() + province = cleaned_data.get('province') + municipality_id = self.data.get('municipality') + + if province and municipality_id: + try: + municipality = Municipality.objects.get(id=municipality_id, province=province) + cleaned_data['municipality'] = municipality + except Municipality.DoesNotExist: + self.add_error('municipality', _("The selected municipality does not belong to the selected province.")) + elif province: + self.add_error('municipality', _("You must select a municipality.")) + + return cleaned_data + def clean_avatar(self): """ Performs custom validation on the uploaded avatar file to strictly enforce diff --git a/web_app/management/__init__.py b/web_app/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_app/management/commands/__init__.py b/web_app/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/web_app/management/commands/load_municipalities.py b/web_app/management/commands/load_municipalities.py new file mode 100644 index 0000000..eefe039 --- /dev/null +++ b/web_app/management/commands/load_municipalities.py @@ -0,0 +1,59 @@ +import json +from pathlib import Path + +from django.core.management.base import BaseCommand + +from web_app.models import Province, Municipality + + +class Command(BaseCommand): + help = "Loads provinces and municipalities from a JSON file into the database." + + def add_arguments(self, parser): + parser.add_argument( + '--source', + type=str, + default=None, + help='Path to the JSON data file. Defaults to data/municipalities_spain.json', + ) + + def handle(self, *args, **options): + source = options.get('source') + if not source: + source = Path(__file__).resolve().parents[3] / 'data' / 'municipalities_spain.json' + + source_path = Path(source) + + if not source_path.exists(): + self.stderr.write(self.style.ERROR(f"File not found: {source_path}")) + return + + with open(source_path, encoding='utf-8') as f: + data = json.load(f) + + provinces_created = 0 + municipalities_created = 0 + + for province_data in data: + province, created = Province.objects.update_or_create( + ine_code=province_data['ine_code'], + defaults={'name': province_data['name']}, + ) + if created: + provinces_created += 1 + self.stdout.write(f" Created province: {province.name}") + + for municipality_data in province_data.get('municipalities', []): + municipality, created = Municipality.objects.update_or_create( + ine_code=municipality_data['ine_code'], + defaults={ + 'name': municipality_data['name'], + 'province': province, + }, + ) + if created: + municipalities_created += 1 + + self.stdout.write(self.style.SUCCESS( + f"Done. {provinces_created} provinces and {municipalities_created} municipalities loaded." + )) diff --git a/web_app/migrations/0004_municipality_province_remove_customuser_location_and_more.py b/web_app/migrations/0004_municipality_province_remove_customuser_location_and_more.py new file mode 100644 index 0000000..52b41c2 --- /dev/null +++ b/web_app/migrations/0004_municipality_province_remove_customuser_location_and_more.py @@ -0,0 +1,64 @@ +# Generated by Django 6.0.3 on 2026-06-09 09:06 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('web_app', '0003_roleauditlog'), + ] + + operations = [ + migrations.CreateModel( + name='Municipality', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ine_code', models.CharField(max_length=5, unique=True)), + ('name', models.CharField(max_length=255)), + ], + options={ + 'verbose_name': 'Municipio', + 'verbose_name_plural': 'Municipios', + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='Province', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ine_code', models.CharField(max_length=2, unique=True)), + ('name', models.CharField(max_length=255)), + ], + options={ + 'verbose_name': 'Provincia', + 'verbose_name_plural': 'Provincias', + 'ordering': ['name'], + }, + ), + migrations.RemoveField( + model_name='customuser', + name='location', + ), + migrations.AddField( + model_name='customuser', + name='age_range', + field=models.CharField(blank=True, choices=[('<18', 'Under 18'), ('18-30', '18-30'), ('31-50', '31-50'), ('>50', 'Over 50')], max_length=10, null=True), + ), + migrations.AddField( + model_name='customuser', + name='gender', + field=models.CharField(blank=True, choices=[('Male', 'Male'), ('Female', 'Female'), ('Non-binary', 'Non-binary'), ('Other', 'Other')], max_length=50, null=True), + ), + migrations.AddField( + model_name='customuser', + name='municipality', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='web_app.municipality'), + ), + migrations.AddField( + model_name='municipality', + name='province', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='municipalities', to='web_app.province'), + ), + ] diff --git a/web_app/models.py b/web_app/models.py index caa3cdb..83776c3 100644 --- a/web_app/models.py +++ b/web_app/models.py @@ -9,6 +9,44 @@ class UserType(models.TextChoices): ADMIN = 'Admin', 'Admin' CONSUMER = 'Consumer', 'Consumer' + +class Gender(models.TextChoices): + MALE = 'Male', 'Male' + FEMALE = 'Female', 'Female' + NON_BINARY = 'Non-binary', 'Non-binary' + OTHER = 'Other', 'Other' + +class AgeRange(models.TextChoices): + UNDER_18 = '<18', 'Under 18' + BETWEEN_18_30 = '18-30', '18-30' + BETWEEN_31_50 = '31-50', '31-50' + OVER_50 = '>50', 'Over 50' + +class Province(models.Model): + ine_code = models.CharField(max_length=2, unique=True) + name = models.CharField(max_length=255) + + class Meta: + verbose_name = 'Provincia' + verbose_name_plural = 'Provincias' + ordering = ['name'] + def __str__(self): + return self.name + +class Municipality(models.Model): + ine_code = models.CharField(max_length=5, unique=True) + name = models.CharField(max_length=255) + province = models.ForeignKey(Province, on_delete=models.CASCADE, related_name='municipalities') + + class Meta: + verbose_name = 'Municipio' + verbose_name_plural = 'Municipios' + ordering = ['name'] + + def __str__(self): + return self.name + + class API(models.Model): port = models.IntegerField(unique=True) name = models.CharField(max_length=100, blank=True, default='') @@ -203,11 +241,29 @@ class CustomUser(AbstractUser): """ avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) bio = models.TextField(blank=True, null=True) - location = models.CharField(max_length=255, blank=True, null=True) type = models.CharField( choices=UserType.choices, default=UserType.CONSUMER, ) + gender = models.CharField( + choices=Gender.choices, + max_length=50, + blank=True, + null=True + ) + age_range = models.CharField( + choices=AgeRange.choices, + max_length=10, + blank=True, + null=True + ) + municipality = models.ForeignKey( + Municipality, + on_delete=models.SET_NULL, + blank=True, + null=True, + related_name='users' + ) subscriptions = models.ManyToManyField('API', blank=True, related_name='subscribed_users') def __str__(self): diff --git a/web_app/urls.py b/web_app/urls.py index 193488c..2fe6d87 100644 --- a/web_app/urls.py +++ b/web_app/urls.py @@ -27,6 +27,8 @@ path('terms-use/', views.terms_use, name='terms_use'), path('privacy-policy/', views.privacy_policy, name='privacy_policy'), + path('api/municipalities//', views.municipalities_by_province, name='municipalities_api'), + #path('user/profile', views.profile, name='profile'), #path('user/history', views.history, name='history'), diff --git a/web_app/views.py b/web_app/views.py index b4a32b8..9363e8e 100644 --- a/web_app/views.py +++ b/web_app/views.py @@ -3,8 +3,14 @@ from django.utils.crypto import get_random_string from django.shortcuts import render, redirect, get_object_or_404 from web_app.forms import CustomUserAdminCreationForm, CustomUserCreationForm -from web_app.models import AgeRating, API, CustomUser, Director, Genre, Movie, UserProfile, Series, Contingut, \ - AgeRating, Valoracio, Visualitzacio, SyncLog +from web_app.models import ( + API, Director, Genre, AgeRating, Contingut, + Movie, Series, CustomUser, UserProfile, + Preferits, Valoracio, Visualitzacio, SyncLog, + RoleAuditLog, + Province, Municipality, +) + from web_app import utils import json from itertools import chain @@ -89,7 +95,7 @@ def register_view(request): user.subscriptions.set(platforms) login(request, user) return redirect('home') - return render(request, 'identify/register.html', {'form': form}) + return render(request, 'identify/register.html', {'form': form, 'provinces': Province.objects.all()}) @login_required(login_url='login') def redirect_by_role(request): @@ -252,7 +258,7 @@ def register_platform_click(request): # MAGIA DE DJANGO: Si existe, actualiza la fecha. Si no, lo crea. vis, created = Visualitzacio.objects.update_or_create( user=request.user, - contingut_id=contingut_id, + contingut_id=contingut_id, api_id=api_id, defaults={'data_visualitzacio': timezone.now()} ) @@ -266,6 +272,9 @@ def register_platform_click(request): return JsonResponse({'status': 'error', 'message': 'Método no permitido'}, status=405) +def municipalities_by_province(request, province_id): + municipalities = Municipality.objects.filter(province_id=province_id).values('id', 'name') + return JsonResponse(list(municipalities), safe=False) @user_passes_test(lambda u: u.is_superuser) def system_logs(request): @@ -278,4 +287,4 @@ def system_logs(request): return render(request, 'users/parts/system_logs.html', { 'logs': page_obj - }) \ No newline at end of file + })