From 81db288158d45189ea823c66049608c5329c55f8 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 4 May 2018 08:46:37 -0500 Subject: [PATCH 01/72] Add base and index templates for reports and participant-idea report --- ideas/migrations/0001_initial.py | 3 ++- ideas/models.py | 1 + reports/templates/base.html | 15 +++++++++++++++ reports/templates/index.html | 15 ++++----------- reports/templates/participants_idea.html | 24 ++++++++++++++++++++++++ reports/urls.py | 3 +++ reports/views.py | 8 ++++++++ 7 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 reports/templates/base.html create mode 100644 reports/templates/participants_idea.html diff --git a/ideas/migrations/0001_initial.py b/ideas/migrations/0001_initial.py index 5c85066..464539f 100644 --- a/ideas/migrations/0001_initial.py +++ b/ideas/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-04-29 05:13 +# Generated by Django 1.10.8 on 2018-05-04 13:41 from __future__ import unicode_literals from django.conf import settings @@ -36,6 +36,7 @@ class Migration(migrations.Migration): ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ + 'ordering': ['idea'], 'verbose_name': 'team member', 'verbose_name_plural': 'groups', }, diff --git a/ideas/models.py b/ideas/models.py index 693be23..fa819c0 100644 --- a/ideas/models.py +++ b/ideas/models.py @@ -25,6 +25,7 @@ def __str__(self): return self.idea.title class Meta(object): + ordering = ['idea'] unique_together = ('idea', 'user') verbose_name = 'team member' verbose_name_plural = 'groups' diff --git a/reports/templates/base.html b/reports/templates/base.html new file mode 100644 index 0000000..4b9f5e6 --- /dev/null +++ b/reports/templates/base.html @@ -0,0 +1,15 @@ + + + + + + + Belatrix Events Reports + + + {% block content %} + {% endblock content %} + + + diff --git a/reports/templates/index.html b/reports/templates/index.html index e7d14b6..19944bd 100644 --- a/reports/templates/index.html +++ b/reports/templates/index.html @@ -1,15 +1,8 @@ - - - - - - - Belatrix Events Reports - +{% extends 'base.html' %} +{% block content %}

Reports

-

+

- +{% endblock content %} diff --git a/reports/templates/participants_idea.html b/reports/templates/participants_idea.html new file mode 100644 index 0000000..443819e --- /dev/null +++ b/reports/templates/participants_idea.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block content %} + + + + + + + + + {% for register in participants_list %} + + + + + + + + {% empty %} + + {% endfor %} +
#Idea TitleParticipant FirstnameParticipant LastnameParticipant email
{{ forloop.counter }}{{ register.idea }}{{ register.user.first_name }}{{ register.user.last_name }}{{ register.user.email }}
no content.
+

Back

+{% endblock content %} \ No newline at end of file diff --git a/reports/urls.py b/reports/urls.py index 8b60032..a9806da 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -1,6 +1,9 @@ from django.conf.urls import url + from .views import index +from .views import participants_idea urlpatterns = [ url(r'^$', index, name='index'), + url(r'^participants/idea/$', participants_idea, name='participants_idea'), ] diff --git a/reports/views.py b/reports/views.py index 4c96023..c939564 100644 --- a/reports/views.py +++ b/reports/views.py @@ -1,5 +1,13 @@ from django.shortcuts import render +from ideas.models import IdeaParticipant + def index(request): return render(request, 'index.html') + + +def participants_idea(request): + participants_list = IdeaParticipant.objects.all() + context = {'participants_list': participants_list} + return render(request, 'participants_idea.html', context) From 39e4bf39247e0d78ed9886f9e22dbfa4ba71fee0 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Tue, 8 May 2018 14:08:18 -0500 Subject: [PATCH 02/72] Add is_moderator and full_name fields to user model --- participants/admin.py | 6 +- participants/migrations/0001_initial.py | 4 +- participants/models.py | 2 + sample_data/users.json | 74 +++++++++++++++++++++++-- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/participants/admin.py b/participants/admin.py index 8ce222c..1bca991 100644 --- a/participants/admin.py +++ b/participants/admin.py @@ -22,13 +22,15 @@ def clean_password(self): class UserCustomAdmin(ImportExportMixin, BaseUserAdmin): form = UserChangeForm - list_display = ("email", "first_name", "last_name", "is_staff", "is_jury", "is_password_reset_required") + list_display = ("email", "full_name", "is_staff", "is_moderator", "is_jury", "is_password_reset_required") fieldsets = ( (None, {'fields': ('email', 'password')}), ('Personal info', {'fields': ('first_name', - 'last_name',)}), + 'last_name', + 'full_name',)}), ('Permissions', {'fields': ('is_superuser', 'is_staff', + 'is_moderator', 'is_active', 'is_blocked', 'is_jury', diff --git a/participants/migrations/0001_initial.py b/participants/migrations/0001_initial.py index e2fb223..44d5ad1 100644 --- a/participants/migrations/0001_initial.py +++ b/participants/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-04-18 13:30 +# Generated by Django 1.10.8 on 2018-05-08 18:19 from __future__ import unicode_literals from django.db import migrations, models @@ -25,9 +25,11 @@ class Migration(migrations.Migration): ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first_name')), ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last_name')), + ('full_name', models.CharField(blank=True, max_length=255, null=True, unique=True)), ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='date joined')), ('is_staff', models.BooleanField(default=False, verbose_name='is staff')), ('is_jury', models.BooleanField(default=False)), + ('is_moderator', models.BooleanField(default=False)), ('is_active', models.BooleanField(default=True, verbose_name='active')), ('is_blocked', models.BooleanField(default=False)), ('is_password_reset_required', models.BooleanField(default=True)), diff --git a/participants/models.py b/participants/models.py index 9cf7fa5..a8b5946 100644 --- a/participants/models.py +++ b/participants/models.py @@ -18,10 +18,12 @@ class User(AbstractBaseUser, PermissionsMixin): email = models.EmailField(_('email address'), unique=True) first_name = models.CharField(_('first_name'), max_length=30, blank=True) last_name = models.CharField(_('last_name'), max_length=30, blank=True) + full_name = models.CharField(max_length=255, blank=True, null=True, unique=True) date_joined = models.DateTimeField(_('date joined'), auto_now_add=True) is_staff = models.BooleanField(_('is staff'), default=False) is_jury = models.BooleanField(default=False) + is_moderator = models.BooleanField(default=False) is_active = models.BooleanField(_('active'), default=True) is_blocked = models.BooleanField(default=False) diff --git a/sample_data/users.json b/sample_data/users.json index 1da22ca..24d756b 100644 --- a/sample_data/users.json +++ b/sample_data/users.json @@ -3,19 +3,21 @@ "pk": 1, "fields": { "password": "pbkdf2_sha256$30000$wck7Fjg9YlVJ$rMoCsMakvkI1oRmAlWCpvtNMmT6MVyOcrErcnaZWLOM=", - "last_login": "2018-04-29T05:16:11.294Z", + "last_login": "2018-05-08T18:21:39Z", "is_superuser": true, "email": "sinfante@belatrixsf.com", "first_name": "Sergio", "last_name": "Infante", + "full_name": "Sergio Infante", "date_joined": "2018-04-13T03:28:41.467Z", "is_staff": true, "is_jury": true, + "is_moderator": true, "is_active": true, "is_blocked": false, "is_password_reset_required": false, "reset_password_code": null, - "temporary_password": null, + "temporary_password": "", "groups": [], "user_permissions": [] } @@ -29,14 +31,16 @@ "email": "participante@email.com", "first_name": "Participante", "last_name": "Registrado", + "full_name": "Participante Registrado", "date_joined": "2018-04-14T14:05:08.636Z", "is_staff": false, "is_jury": false, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": true, "reset_password_code": null, - "temporary_password": null, + "temporary_password": "", "groups": [], "user_permissions": [] } @@ -50,14 +54,16 @@ "email": "usuario@email.com", "first_name": "Usuario", "last_name": "No participante", + "full_name": "Usuario No participante", "date_joined": "2018-04-14T14:08:13.983Z", "is_staff": false, "is_jury": false, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": true, "reset_password_code": null, - "temporary_password": null, + "temporary_password": "", "groups": [], "user_permissions": [] } @@ -71,14 +77,16 @@ "email": "organizador@email.com", "first_name": "Organizador", "last_name": "Hackatrix", + "full_name": "Organizador Hackatrix", "date_joined": "2018-04-14T14:10:13.543Z", "is_staff": true, "is_jury": false, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": true, "reset_password_code": null, - "temporary_password": null, + "temporary_password": "", "groups": [], "user_permissions": [] } @@ -92,9 +100,11 @@ "email": "jurado@email.com", "first_name": "Jurado", "last_name": "Hackatrix", + "full_name": "Jurado Hackatrix", "date_joined": "2018-04-14T15:28:12.191Z", "is_staff": false, "is_jury": true, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": true, @@ -113,9 +123,11 @@ "email": "developer@email.com", "first_name": "developer", "last_name": "hackatrix", + "full_name": "Developer Hackatrix", "date_joined": "2018-04-24T15:05:49.752Z", "is_staff": true, "is_jury": false, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": false, @@ -134,9 +146,11 @@ "email": "qa@email.com", "first_name": "qa", "last_name": "hackatrix", + "full_name": "QA Hackatrix", "date_joined": "2018-04-24T15:07:03.261Z", "is_staff": true, "is_jury": false, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": false, @@ -155,9 +169,11 @@ "email": "jurado02@email.com", "first_name": "Segundo", "last_name": "Jurado", + "full_name": "Segundo Jurado", "date_joined": "2018-04-29T05:16:38.638Z", "is_staff": false, "is_jury": true, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": true, @@ -176,9 +192,11 @@ "email": "participante02@email.com", "first_name": "Segundo", "last_name": "Participante", + "full_name": "Segundo Participante", "date_joined": "2018-04-29T05:17:34.685Z", "is_staff": false, "is_jury": false, + "is_moderator": false, "is_active": true, "is_blocked": false, "is_password_reset_required": true, @@ -187,4 +205,50 @@ "groups": [], "user_permissions": [] } +}, { + "model": "participants.user", + "pk": 10, + "fields": { + "password": "pbkdf2_sha256$30000$k5Wh7imf3GJq$0fYP9VVDRCGrfFJb+1v9huWI6C8Ywb8LEbyH/wjHQk8=", + "last_login": null, + "is_superuser": false, + "email": "dvelasquez@belatrixsf.com", + "first_name": "", + "last_name": "", + "full_name": "Diego Velasquez", + "date_joined": "2018-05-08T18:24:29.061Z", + "is_staff": true, + "is_jury": false, + "is_moderator": true, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": false, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 11, + "fields": { + "password": "pbkdf2_sha256$30000$sauev23jG2VL$cTOLZlAM75F4nreF3K3UfUHsnqF/pEYs6csjEaJm34c=", + "last_login": null, + "is_superuser": false, + "email": "kcerron@belatrixsf.com", + "first_name": "", + "last_name": "", + "full_name": "Karla Cerron", + "date_joined": "2018-05-08T18:25:19.632Z", + "is_staff": true, + "is_jury": true, + "is_moderator": true, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": false, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } }] \ No newline at end of file From be0d2278bc3f93cf74bf84dd21dc8b060765eb27 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Tue, 8 May 2018 20:41:02 -0500 Subject: [PATCH 03/72] Remove first_name and last_name fields from user serializer, and add full_name and is_moderator --- ideas/serializers.py | 2 +- participants/serializers.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ideas/serializers.py b/ideas/serializers.py index 73e95be..f9b07f7 100644 --- a/ideas/serializers.py +++ b/ideas/serializers.py @@ -8,7 +8,7 @@ class UserSerializer(serializers.ModelSerializer): class Meta(object): model = User - fields = ('id', 'first_name', 'last_name', 'email') + fields = ('id', 'full_name', 'email') class EventSerializer(serializers.ModelSerializer): diff --git a/participants/serializers.py b/participants/serializers.py index d655762..0618b49 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -7,8 +7,8 @@ class Meta(object): model = User fields = ('id', 'email', - 'first_name', - 'last_name', + 'full_name', + 'is_moderator', 'is_staff', 'is_active', 'is_jury', From d3d57546eb26ccdca9797fdf17bbf3e5027337e1 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 09:28:39 -0500 Subject: [PATCH 04/72] Add query optional parameter for city on event views to improve swagger interaction for development --- events/views.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/events/views.py b/events/views.py index 8d73a6b..f4088f6 100644 --- a/events/views.py +++ b/events/views.py @@ -32,6 +32,14 @@ def event_detail(request, event_id): def event_featured(request): """ Returns event featured + --- + GET: + parameters: + - name: city + description: set city_id to filter events by city + type: string + required: false + paramType: query """ events = Event.objects.filter(is_active=True, is_featured=True) @@ -68,6 +76,14 @@ def event_featured(request): def event_interaction(request, event_id): """ Returns event interactions + --- + GET: + parameters: + - name: pagination + description: set true if you want paginated results + type: string + required: false + paramType: query """ event = get_object_or_404(Event, pk=event_id, is_active=True) interactions = Interaction.objects.filter(event=event, is_active=True) @@ -102,6 +118,14 @@ def event_interaction_vote(request, interaction_id): def event_list(request): """ Returns event list + --- + GET: + parameters: + - name: city + description: set city_id to filter events by city + type: string + required: false + paramType: query """ events = Event.objects.all() if request.GET.get('city'): @@ -126,6 +150,14 @@ def event_list(request): def event_upcoming_list(request): """ Returns upcoming event list + --- + GET: + parameters: + - name: city + description: set city_id to filter events by city + type: string + required: false + paramType: query """ events = Event.objects.filter(is_upcoming=True, is_active=True) if request.GET.get('city'): @@ -150,6 +182,14 @@ def event_upcoming_list(request): def event_past_list(request): """ Returns past event list + --- + GET: + parameters: + - name: city + description: set city_id to filter events by city + type: string + required: false + paramType: query """ events = Event.objects.filter(is_upcoming=False, is_active=True) if request.GET.get('city'): From b906da6276fc3a87048722d10203e749fe038013 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 12:22:53 -0500 Subject: [PATCH 05/72] Add full_name fields instead first_name and last_name for participants. --- participants/admin.py | 2 +- .../management/commands/checkparticipant.py | 2 ++ participants/migrations/0001_initial.py | 5 ++--- participants/models.py | 3 +-- participants/views.py | 2 ++ sample_data/participants.json | 14 +++++--------- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/participants/admin.py b/participants/admin.py index 1bca991..8d386f7 100644 --- a/participants/admin.py +++ b/participants/admin.py @@ -51,7 +51,7 @@ class UserCustomAdmin(ImportExportMixin, BaseUserAdmin): class ParticipantForm(ImportExportMixin, admin.ModelAdmin): - list_display = ("email", "first_name", "last_name", "event_id") + list_display = ("email", "full_name", "event_id") admin.site.register(Participant, ParticipantForm) diff --git a/participants/management/commands/checkparticipant.py b/participants/management/commands/checkparticipant.py index 0b8160d..915e7c2 100644 --- a/participants/management/commands/checkparticipant.py +++ b/participants/management/commands/checkparticipant.py @@ -17,6 +17,8 @@ def check_participant(self, event_id): user = User.objects.filter(email=participant.email) if len(user) == 1: user_registered = user[0] + user_registered.full_name = participant.full_name + user_registered.save() EventParticipant.objects.create(event=event, participant=user_registered) def handle(self, *args, **options): diff --git a/participants/migrations/0001_initial.py b/participants/migrations/0001_initial.py index 44d5ad1..bfb83eb 100644 --- a/participants/migrations/0001_initial.py +++ b/participants/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-08 18:19 +# Generated by Django 1.10.8 on 2018-05-09 15:08 from __future__ import unicode_literals from django.db import migrations, models @@ -50,8 +50,7 @@ class Migration(migrations.Migration): name='Participant', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('first_name', models.CharField(max_length=255)), - ('last_name', models.CharField(max_length=255)), + ('full_name', models.CharField(max_length=255)), ('email', models.EmailField(max_length=254, unique=True)), ('event_id', models.IntegerField(default=0)), ], diff --git a/participants/models.py b/participants/models.py index a8b5946..61e8920 100644 --- a/participants/models.py +++ b/participants/models.py @@ -65,8 +65,7 @@ def generate_reset_password_code(self): class Participant(models.Model): - first_name = models.CharField(max_length=255) - last_name = models.CharField(max_length=255) + full_name = models.CharField(max_length=255) email = models.EmailField(unique=True) event_id = models.IntegerField(default=0) diff --git a/participants/views.py b/participants/views.py index 170e95a..3a8464c 100644 --- a/participants/views.py +++ b/participants/views.py @@ -62,6 +62,8 @@ def user_creation(request): if len(participant) == 1: event = Event.objects.filter(pk=participant[0].event_id) if len(event) == 1: + new_user.full_name = participant[0].full_name + new_user.save() EventParticipant.objects.create(event=event[0], participant=new_user) try: diff --git a/sample_data/participants.json b/sample_data/participants.json index b0f7035..3dde8b5 100644 --- a/sample_data/participants.json +++ b/sample_data/participants.json @@ -2,8 +2,7 @@ "model": "participants.participant", "pk": 1, "fields": { - "first_name": "Otro Participante", - "last_name": "Registrado", + "full_name": "Otro Participante Registrado", "email": "otroparticipante@email.com", "event_id": 19 } @@ -11,8 +10,7 @@ "model": "participants.participant", "pk": 2, "fields": { - "first_name": "Segundo", - "last_name": "Participante", + "full_name": "Segundo Participante", "email": "participante02@email.com", "event_id": 19 } @@ -20,8 +18,7 @@ "model": "participants.participant", "pk": 3, "fields": { - "first_name": "Tercer", - "last_name": "Participante", + "full_name": "Tercer Participante", "email": "participante03@email.com", "event_id": 19 } @@ -29,9 +26,8 @@ "model": "participants.participant", "pk": 4, "fields": { - "first_name": "Participante", - "last_name": "Registrado", - "email": "participante@email.com", + "full_name": "Karla Cerron", + "email": "kcerron@belatrixsf.com", "event_id": 19 } }] \ No newline at end of file From 6d6a353edb5cd8b41f00feff546989d6c384b9ab Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 14:00:13 -0500 Subject: [PATCH 06/72] Add edit fullname user endpoint --- participants/serializers.py | 4 ++++ participants/urls.py | 3 ++- participants/views.py | 25 ++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/participants/serializers.py b/participants/serializers.py index 0618b49..65c374e 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -19,6 +19,10 @@ class UserCreationSerializer(serializers.Serializer): email = serializers.CharField(max_length=50) +class UserFullnameSerializer(serializers.Serializer): + full_name = serializers.CharField(max_length=255) + + class UserAuthenticationSerializer(serializers.Serializer): username = serializers.CharField(max_length=100) password = serializers.CharField(max_length=100) diff --git a/participants/urls.py b/participants/urls.py index 6f3b495..dc6136d 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -1,5 +1,5 @@ from django.conf.urls import url -from .views import user_detail, user_creation, user_update_password, CustomAuthToken +from .views import user_detail, user_creation, user_update, user_update_password, CustomAuthToken from .views import user_password_recovery_request, user_password_recovery_confirmation @@ -9,6 +9,7 @@ url(r'^authenticate/', CustomAuthToken.as_view()), url(r'^create/$', user_creation, name='user_creation'), url(r'^recover/$', user_password_recovery_request, name='user_password_recovery_request'), + url(r'^update/$', user_update, name='user_update'), url(r'^recover/(?P[0-9a-z-]+)$', user_password_recovery_confirmation, name='user_password_recovery_confirmation'), diff --git a/participants/views.py b/participants/views.py index 3a8464c..31b433d 100644 --- a/participants/views.py +++ b/participants/views.py @@ -12,7 +12,8 @@ from rest_framework.renderers import StaticHTMLRenderer from rest_framework.response import Response from .models import User, Participant -from .serializers import UserSerializer, UserCreationSerializer, UserUpdatePasswordSerializer +from .serializers import UserSerializer, UserCreationSerializer +from .serializers import UserUpdatePasswordSerializer, UserFullnameSerializer from events.models import Event, EventParticipant @@ -78,6 +79,28 @@ def user_creation(request): return Response(serializer.data, status=status.HTTP_201_CREATED) +@api_view(['PATCH', ]) +@permission_classes((IsAuthenticated, )) +def user_update(request): + """ + Update user data + --- + PATCH: + serializer: participants.serializers.UserFullnameSerializer + response_serializer: participants.serializers.UserSerializer + """ + if request.method == 'PATCH': + serializer = UserFullnameSerializer(data=request.data) + + if serializer.is_valid(raise_exception=True): + full_name = serializer.validated_data['full_name'] + current_user = request.user + current_user.full_name = full_name + current_user.save() + serializer = UserSerializer(current_user) + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + + @api_view(['PATCH', ]) @permission_classes((IsAuthenticated, )) def user_update_password(request, user_id): From e6b333e345a0efe2078059d1a1648ab319765bee Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 14:07:56 -0500 Subject: [PATCH 07/72] Remove user_id from update password endpoint --- participants/urls.py | 2 +- participants/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/participants/urls.py b/participants/urls.py index dc6136d..6c4ed29 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -5,11 +5,11 @@ urlpatterns = [ url(r'^(?P\d+)/$', user_detail, name='user_detail'), - url(r'^(?P\d+)/update/password/$', user_update_password, name='user_update_password'), url(r'^authenticate/', CustomAuthToken.as_view()), url(r'^create/$', user_creation, name='user_creation'), url(r'^recover/$', user_password_recovery_request, name='user_password_recovery_request'), url(r'^update/$', user_update, name='user_update'), + url(r'^update/password/$', user_update_password, name='user_update_password'), url(r'^recover/(?P[0-9a-z-]+)$', user_password_recovery_confirmation, name='user_password_recovery_confirmation'), diff --git a/participants/views.py b/participants/views.py index 31b433d..f17a740 100644 --- a/participants/views.py +++ b/participants/views.py @@ -103,7 +103,7 @@ def user_update(request): @api_view(['PATCH', ]) @permission_classes((IsAuthenticated, )) -def user_update_password(request, user_id): +def user_update_password(request): """ Update user password --- @@ -118,7 +118,7 @@ def user_update_password(request, user_id): current_password = serializer.validated_data['current_password'] new_password = serializer.validated_data['new_password'] - user = get_object_or_404(User, pk=user_id) + user = request.user if current_password == new_password: raise ValidationError('Passwords iguales') From fa0c30c9feb1df234ac177d47a2e2470f6628992 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 14:34:18 -0500 Subject: [PATCH 08/72] Add logout user endpoint --- participants/urls.py | 3 ++- participants/views.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/participants/urls.py b/participants/urls.py index 6c4ed29..4ecf6d4 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -1,12 +1,13 @@ from django.conf.urls import url from .views import user_detail, user_creation, user_update, user_update_password, CustomAuthToken -from .views import user_password_recovery_request, user_password_recovery_confirmation +from .views import user_password_recovery_request, user_password_recovery_confirmation, user_logout urlpatterns = [ url(r'^(?P\d+)/$', user_detail, name='user_detail'), url(r'^authenticate/', CustomAuthToken.as_view()), url(r'^create/$', user_creation, name='user_creation'), + url(r'^logout/$', user_logout, name='user_logout'), url(r'^recover/$', user_password_recovery_request, name='user_password_recovery_request'), url(r'^update/$', user_update, name='user_update'), url(r'^update/password/$', user_update_password, name='user_update_password'), diff --git a/participants/views.py b/participants/views.py index f17a740..d2ff315 100644 --- a/participants/views.py +++ b/participants/views.py @@ -1,5 +1,6 @@ from django.core.mail import EmailMessage from django.core.urlresolvers import reverse +from django.contrib.auth import logout from django.contrib.sites.models import Site from django.shortcuts import get_object_or_404 from re import match as regex_match @@ -213,3 +214,13 @@ def post(self, request, *args, **kwargs): 'is_jury': user.is_jury, 'is_password_reset_required': user.is_password_reset_required, }) + + +@api_view(['POST', ]) +@permission_classes((IsAuthenticated, )) +def user_logout(request): + """ + Logout current user + """ + logout(request) + return Response(status=status.HTTP_202_ACCEPTED) From a7d3490e3b6649d921d31b0f3cfb282a5446a4b3 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 17:04:15 -0500 Subject: [PATCH 09/72] Add user profile endpoint --- participants/urls.py | 4 +++- participants/views.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/participants/urls.py b/participants/urls.py index 4ecf6d4..08a0860 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import url -from .views import user_detail, user_creation, user_update, user_update_password, CustomAuthToken +from .views import user_profile, user_detail +from .views import user_creation, user_update, user_update_password, CustomAuthToken from .views import user_password_recovery_request, user_password_recovery_confirmation, user_logout @@ -8,6 +9,7 @@ url(r'^authenticate/', CustomAuthToken.as_view()), url(r'^create/$', user_creation, name='user_creation'), url(r'^logout/$', user_logout, name='user_logout'), + url(r'^profile/$', user_profile, name='user_profile'), url(r'^recover/$', user_password_recovery_request, name='user_password_recovery_request'), url(r'^update/$', user_update, name='user_update'), url(r'^update/password/$', user_update_password, name='user_update_password'), diff --git a/participants/views.py b/participants/views.py index d2ff315..20a6f6f 100644 --- a/participants/views.py +++ b/participants/views.py @@ -32,6 +32,19 @@ def user_detail(request, user_id): return Response(serializer.data, status=status.HTTP_200_OK) +@api_view(['GET', ]) +@permission_classes((IsAuthenticated, )) +def user_profile(request): + """ + Returns user profile + --- + GET: + response_serializer: participants.serializers.UserSerializer + """ + serializer = UserSerializer(request.user) + return Response(serializer.data, status=status.HTTP_200_OK) + + @api_view(['POST', ]) def user_creation(request): """ From 91af466db0a6c8d6fa1f04c6311573a025a22582 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 18:09:37 -0500 Subject: [PATCH 10/72] Idea participants endpoint is just for authenticated users --- ideas/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ideas/views.py b/ideas/views.py index 3c41b5b..c9f6588 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -83,7 +83,7 @@ def idea_create(request): @api_view(['GET']) -@permission_classes((IsAuthenticatedOrReadOnly, )) +@permission_classes((IsAuthenticated, )) def idea_participants(request, idea_id): """ Endpoint to get participant list group by idea From df2bc9fc62348a65e93292f5bb88100f862e5bcd Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 19:17:22 -0500 Subject: [PATCH 11/72] Prevent user register in other idea in same event --- ideas/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ideas/views.py b/ideas/views.py index c9f6588..6a46828 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -115,6 +115,13 @@ def idea_register(request, idea_id): if serializer.is_valid(raise_exception=True): idea = get_object_or_404(Idea, pk=idea_id) user = get_object_or_404(User, pk=serializer.validated_data['user_id']) + + previous_records = IdeaParticipant.objects.filter(user=user) + if len(previous_records) > 0: + for record in previous_records: + if record.idea.event == idea.event: + raise NotAcceptable('Ya se registro en una idea para este evento.') + number_participants = IdeaParticipant.objects.filter(idea=idea).count() if config.TEAM_MAX_SIZE > number_participants and idea.is_completed is False: try: From b659ae1ce28b45a19737281a79ee6f60051fdce1 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 20:28:47 -0500 Subject: [PATCH 12/72] Add is_valid field to idea model, views and serializers --- ideas/admin.py | 2 +- ideas/migrations/0001_initial.py | 6 +- ideas/models.py | 4 + ideas/serializers.py | 2 +- sample_data/ideas.json | 128 +++++++++++++++++++++++-------- 5 files changed, 107 insertions(+), 35 deletions(-) diff --git a/ideas/admin.py b/ideas/admin.py index bdce221..b6d5e0d 100644 --- a/ideas/admin.py +++ b/ideas/admin.py @@ -5,7 +5,7 @@ class IdeaAdmin(ImportExportMixin, admin.ModelAdmin): - list_display = ('title', 'author', 'event', 'is_completed') + list_display = ('title', 'author', 'event', 'is_valid', 'is_completed') class IdeaParticipantAdmin(admin.ModelAdmin): diff --git a/ideas/migrations/0001_initial.py b/ideas/migrations/0001_initial.py index 464539f..7dcf104 100644 --- a/ideas/migrations/0001_initial.py +++ b/ideas/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-04 13:41 +# Generated by Django 1.10.8 on 2018-05-10 01:26 from __future__ import unicode_literals from django.conf import settings @@ -24,9 +24,13 @@ class Migration(migrations.Migration): ('title', models.CharField(max_length=255, unique=True)), ('description', models.TextField(blank=True, null=True)), ('is_completed', models.BooleanField(default=False)), + ('is_valid', models.BooleanField(default=False)), ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event')), ], + options={ + 'ordering': ['title'], + }, ), migrations.CreateModel( name='IdeaParticipant', diff --git a/ideas/models.py b/ideas/models.py index fa819c0..757cf32 100644 --- a/ideas/models.py +++ b/ideas/models.py @@ -11,10 +11,14 @@ class Idea(models.Model): author = models.ForeignKey('participants.User') event = models.ForeignKey('events.Event') is_completed = models.BooleanField(default=False) + is_valid = models.BooleanField(default=False) def __str__(self): return self.title + class Meta(object): + ordering = ['title'] + @python_2_unicode_compatible class IdeaParticipant(models.Model): diff --git a/ideas/serializers.py b/ideas/serializers.py index f9b07f7..ecf4e36 100644 --- a/ideas/serializers.py +++ b/ideas/serializers.py @@ -30,7 +30,7 @@ class IdeaSerializer(serializers.ModelSerializer): class Meta(object): model = Idea - fields = ('id', 'author', 'title', 'description', 'event', 'is_completed') + fields = ('id', 'author', 'title', 'description', 'event', 'is_completed', 'is_valid') depth = 1 diff --git a/sample_data/ideas.json b/sample_data/ideas.json index 243db7b..0469dee 100644 --- a/sample_data/ideas.json +++ b/sample_data/ideas.json @@ -5,7 +5,9 @@ "title": "Agro IoT - Sistema en arduino para ver cultivos en tiempo real", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -14,7 +16,9 @@ "title": "Auto Check - App para obtener informaci\u00f3n del veh\u00edculo", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -23,7 +27,9 @@ "title": "Encuentrame.pe - B\u00fasqueda de personas por medio de reconocimiento facial", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -32,7 +38,9 @@ "title": "Social Faith - Sistema para promocionar campa\u00f1as de donaciones para ONG", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -41,7 +49,9 @@ "title": "Entiende.pe - Plataforma para aprender de forma inteligente usando inteligencia artificial", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -50,7 +60,9 @@ "title": "Ayudame.pe - Plataforma que sirve como agente de cambio para mejorar la calidad de vida de personas", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -59,7 +71,9 @@ "title": "Maiden Assistant - App que ofrece recomendaciones basados en la experiencia de la persona", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -68,7 +82,9 @@ "title": "Estudia Por Gusto - App que ayuda a intercambiar conocimiento de forma interactiva", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -77,7 +93,9 @@ "title": "Blood care - Plataforma para encontrar una red de donantes de sangre voluntarios", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -86,7 +104,9 @@ "title": "Trappist - Plataforma que nos ayuda a optimizar el tiempo de espera en los bancos usando sensores", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -95,7 +115,9 @@ "title": "Ayllu FX - App para conectar y notificar personas para intercambiar divisas", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -104,7 +126,9 @@ "title": "I do it - Plataforma para conectar personas que requieren servicios de utilidad", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -113,7 +137,9 @@ "title": "Aprende, Responde, Gana", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -122,7 +148,9 @@ "title": "YoTeAyudo - App para voluntarios con el objetivo de ayudar y colaborar con los dem\u00e1s", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -131,7 +159,9 @@ "title": "Parqueate - App para buscar zonas de estacionamiento", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -140,7 +170,9 @@ "title": "ReciclaPe - App m\u00f3vil para mejorar el proceso de reciclaje", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -149,7 +181,9 @@ "title": "EcoFriends - App de retos ecol\u00f3gicos para minimizar el impacto con la naturaleza", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -158,7 +192,9 @@ "title": "Co-Aprendamos.pe - Retos para aprender", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -167,7 +203,9 @@ "title": "Smart Cacao - IOT aplicado a la agricultura, automatizar parcelas agricolas", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -176,7 +214,9 @@ "title": "You Study - Comunidad de aprendizaje a trav\u00e9s de grupos de estudio", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -185,7 +225,9 @@ "title": "Regala una Sonrisa - Plataforma que gestiona donaciones de los usuarios para distribuir a ONG", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -194,7 +236,9 @@ "title": "YoTambien - Sistema de capacitaci\u00f3n para personas con discapacidades", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -203,7 +247,9 @@ "title": "Hogar SOS - Marketplace de servicios para el hogar, como carpinter\u00eda, gasfiter\u00eda, entre otros", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -212,7 +258,9 @@ "title": "iChancon - Red social para colaborar en la resoluci\u00f3n de problemas acad\u00e9micos", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -221,7 +269,9 @@ "title": "iMetrics - Medidor de agua inteligente conectado a una red neural para optimizar facturaciones", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -230,7 +280,9 @@ "title": "Mi Primer App - Participaci\u00f3n en proyectos para estudiantes de postgrado", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -239,7 +291,9 @@ "title": "B\u00fasqueda de Personas Perdidas - Plataforma para buscar personas por medio de reconocimiento facial", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -248,7 +302,9 @@ "title": "Amor a Sofia - App para ayuda a los postulantes a universidades", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": true } }, { "model": "ideas.idea", @@ -257,7 +313,9 @@ "title": "Medical Chain - Historiales m\u00e9dicos registrados en una red blockchain", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -266,7 +324,9 @@ "title": "Gento - Plataforma que cambia la forma de como compramos ropa por medio de machine learning", "description": "", "author": 4, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -275,7 +335,9 @@ "title": "Sportify - App social para buscar gente que quiera jugar y este cerca de ti", "description": "", "author": 1, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }, { "model": "ideas.idea", @@ -284,6 +346,8 @@ "title": "Dale Go - App social de eventos deportivos", "description": "", "author": 2, - "event": 19 + "event": 19, + "is_completed": false, + "is_valid": false } }] \ No newline at end of file From c32c106f169b97959b73b6e33b73b673ebd28f6a Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Wed, 9 May 2018 22:23:57 -0500 Subject: [PATCH 13/72] Add views and endpoints to validate ideas by moderators --- ideas/migrations/0001_initial.py | 4 ++-- ideas/models.py | 2 +- ideas/urls.py | 4 +++- ideas/views.py | 37 ++++++++++++++++++++++++++++++-- participants/permissions.py | 7 ++++++ 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/ideas/migrations/0001_initial.py b/ideas/migrations/0001_initial.py index 7dcf104..91d4012 100644 --- a/ideas/migrations/0001_initial.py +++ b/ideas/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-10 01:26 +# Generated by Django 1.10.8 on 2018-05-10 02:56 from __future__ import unicode_literals from django.conf import settings @@ -97,7 +97,7 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='ideavotes', - unique_together=set([('idea', 'participant')]), + unique_together=set([('event', 'participant')]), ), migrations.AlterUniqueTogether( name='ideascores', diff --git a/ideas/models.py b/ideas/models.py index 757cf32..5570ba2 100644 --- a/ideas/models.py +++ b/ideas/models.py @@ -41,7 +41,7 @@ class IdeaVotes(models.Model): participant = models.ForeignKey('participants.User') class Meta(object): - unique_together = ('idea', 'participant') + unique_together = ('event', 'participant') verbose_name_plural = 'votes' diff --git a/ideas/urls.py b/ideas/urls.py index 0b74c09..63b51fd 100644 --- a/ideas/urls.py +++ b/ideas/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import url from .views import idea, idea_create, idea_register, idea_participants, idea_unregister from .views import idea_completed, idea_open, idea_rate, idea_scores_criteria +from .views import idea_validate, idea_draft_list urlpatterns = [ @@ -9,10 +10,11 @@ url(r'^(?P\d+)/open/$', idea_open, name='idea_open'), url(r'^(?P\d+)/register/$', idea_register, name='idea_register'), url(r'^(?P\d+)/unregister/$', idea_unregister, name='idea_unregister'), + url(r'^(?P\d+)/validate/$', idea_validate, name='idea_validate'), url(r'^(?P\d+)/participants/$', idea_participants, name='idea_participants'), url(r'^(?P\d+)/rate/$', idea_rate, name='idea_rate'), # url(r'^(?P\d+)/vote/$', idea_vote, name='idea_vote'), url(r'^create/$', idea_create, name='idea_create'), url(r'^rate/category/list/$', idea_scores_criteria, name='idea_scores_criteria'), - # url(r'^list/event/(?P\d+)/$', idea_list, name='idea_list'), + url(r'^draft/event/(?P\d+)/list$', idea_draft_list, name='idea_draft_list'), ] diff --git a/ideas/views.py b/ideas/views.py index 6a46828..687027b 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -9,7 +9,7 @@ from events.models import Event from participants.models import User -from participants.permissions import IsJury +from participants.permissions import IsJury, IsModerator from .models import Idea, IdeaParticipant, IdeaVotes, IdeaScores, IdeaScoresCriteria from .serializers import IdeaCreationSerializer, IdeaSerializer, IdeaParticipantsSerializer @@ -218,6 +218,20 @@ def idea_list(request, event_id): GET: response_serializer: ideas.serializers.IdeaSerializer """ + ideas = get_list_or_404(Idea, event=event_id, is_valid=True) + serializer = IdeaSerializer(ideas, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + +@api_view(['GET']) +@permission_classes((IsModerator, )) +def idea_draft_list(request, event_id): + """ + Returns idea list by event without filter + --- + GET: + response_serializer: ideas.serializers.IdeaSerializer + """ ideas = get_list_or_404(Idea, event=event_id) serializer = IdeaSerializer(ideas, many=True) return Response(serializer.data, status=status.HTTP_200_OK) @@ -244,7 +258,7 @@ def idea_vote(request, event_id): print(e) raise NotAcceptable('Usuario ya voto') event_ideas = [] - ideas = get_list_or_404(Idea, event=event_id) + ideas = get_list_or_404(Idea, event=event_id, is_valid=True) for idea in ideas: votes = IdeaVotes.objects.filter(idea=idea).count() idea_response = {'id': idea.id, @@ -255,6 +269,25 @@ def idea_vote(request, event_id): return Response(serializer.data, status=status.HTTP_200_OK) +@api_view(['PATCH', ]) +@permission_classes((IsModerator, )) +def idea_validate(request, idea_id): + """ + Mark idea as valid or invalid + --- + PATCH: + response_serializer: ideas.serializers.IdeaSerializer + """ + idea = get_object_or_404(Idea, pk=idea_id) + if idea.is_valid: + idea.is_valid = False + else: + idea.is_valid = True + idea.save() + serializer = IdeaSerializer(idea) + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) + + @api_view(['GET', ]) @permission_classes((IsAuthenticated, IsJury)) def idea_scores_criteria(request): diff --git a/participants/permissions.py b/participants/permissions.py index 0f6f239..64af083 100644 --- a/participants/permissions.py +++ b/participants/permissions.py @@ -17,3 +17,10 @@ def has_permission(self, request, view): participants = Participant.objects.filter(email=request.user.email).count() if participants > 0: return True + + +class IsModerator(permissions.BasePermission): + message = 'User needs to be a moderator.' + + def has_permission(self, request, view): + return request.user and request.user.is_moderator From 2746b545f70e0ff8b1f77ee42ff173e900394c1d Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Thu, 10 May 2018 11:46:45 -0500 Subject: [PATCH 14/72] Add my ideas list to user endpoints --- ideas/views.py | 14 ++++++++++++++ participants/urls.py | 2 ++ 2 files changed, 16 insertions(+) diff --git a/ideas/views.py b/ideas/views.py index 687027b..ac77037 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -237,6 +237,20 @@ def idea_draft_list(request, event_id): return Response(serializer.data, status=status.HTTP_200_OK) +@api_view(['GET']) +@permission_classes((IsAuthenticated, )) +def my_ideas(request): + """ + Returns user idea list, not validation required and is not public. + --- + GET: + response_serializer: ideas.serializers.IdeaSerializer + """ + ideas = Idea.objects.filter(author=request.user) + serializer = IdeaSerializer(ideas, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + @api_view(['GET', 'POST']) @permission_classes((IsAuthenticatedOrReadOnly, )) def idea_vote(request, event_id): diff --git a/participants/urls.py b/participants/urls.py index 08a0860..09bf895 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -2,6 +2,7 @@ from .views import user_profile, user_detail from .views import user_creation, user_update, user_update_password, CustomAuthToken from .views import user_password_recovery_request, user_password_recovery_confirmation, user_logout +from ideas.views import my_ideas urlpatterns = [ @@ -10,6 +11,7 @@ url(r'^create/$', user_creation, name='user_creation'), url(r'^logout/$', user_logout, name='user_logout'), url(r'^profile/$', user_profile, name='user_profile'), + url(r'^ideas/$', my_ideas, name='my_ideas'), url(r'^recover/$', user_password_recovery_request, name='user_password_recovery_request'), url(r'^update/$', user_update, name='user_update'), url(r'^update/password/$', user_update_password, name='user_update_password'), From 708bf50a152fccb0bd3faa7d1add0af18280a0d7 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Thu, 10 May 2018 20:24:09 -0500 Subject: [PATCH 15/72] Add role and phone number fields to user models --- participants/admin.py | 11 ++++++-- participants/migrations/0001_initial.py | 26 +++++++++++++++-- participants/models.py | 17 ++++++++++++ sample_data/role.json | 37 +++++++++++++++++++++++++ sample_data/users.json | 24 +++++++++++++++- 5 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 sample_data/role.json diff --git a/participants/admin.py b/participants/admin.py index 8d386f7..ef7fae6 100644 --- a/participants/admin.py +++ b/participants/admin.py @@ -3,7 +3,11 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.forms import ReadOnlyPasswordHashField from import_export.admin import ImportExportMixin -from .models import Participant, User +from .models import Participant, User, Role + + +class RoleAdmin(admin.ModelAdmin): + list_display = ('name', ) class UserChangeForm(forms.ModelForm): @@ -27,7 +31,9 @@ class UserCustomAdmin(ImportExportMixin, BaseUserAdmin): (None, {'fields': ('email', 'password')}), ('Personal info', {'fields': ('first_name', 'last_name', - 'full_name',)}), + 'full_name', + 'phone_number', + 'role')}), ('Permissions', {'fields': ('is_superuser', 'is_staff', 'is_moderator', @@ -55,4 +61,5 @@ class ParticipantForm(ImportExportMixin, admin.ModelAdmin): admin.site.register(Participant, ParticipantForm) +admin.site.register(Role, RoleAdmin) admin.site.register(User, UserCustomAdmin) diff --git a/participants/migrations/0001_initial.py b/participants/migrations/0001_initial.py index bfb83eb..673676f 100644 --- a/participants/migrations/0001_initial.py +++ b/participants/migrations/0001_initial.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-09 15:08 +# Generated by Django 1.10.8 on 2018-05-11 00:52 from __future__ import unicode_literals +import django.core.validators from django.db import migrations, models +import django.db.models.deletion import participants.managers @@ -27,6 +29,7 @@ class Migration(migrations.Migration): ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last_name')), ('full_name', models.CharField(blank=True, max_length=255, null=True, unique=True)), ('date_joined', models.DateTimeField(auto_now_add=True, verbose_name='date joined')), + ('phone_number', models.CharField(blank=True, max_length=9, null=True, validators=[django.core.validators.RegexValidator(message="Phone number must be entered in the format: '999999999'. Up to 9 digits allowed.", regex='^\\d{9}$')])), ('is_staff', models.BooleanField(default=False, verbose_name='is staff')), ('is_jury', models.BooleanField(default=False)), ('is_moderator', models.BooleanField(default=False)), @@ -36,7 +39,6 @@ class Migration(migrations.Migration): ('reset_password_code', models.UUIDField(blank=True, default=None, null=True)), ('temporary_password', models.CharField(blank=True, max_length=4, null=True)), ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), - ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], options={ 'verbose_name': 'user', @@ -55,4 +57,24 @@ class Migration(migrations.Migration): ('event_id', models.IntegerField(default=0)), ], ), + migrations.CreateModel( + name='Role', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.AddField( + model_name='user', + name='role', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='participants.Role'), + ), + migrations.AddField( + model_name='user', + name='user_permissions', + field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), + ), ] diff --git a/participants/models.py b/participants/models.py index 61e8920..2789665 100644 --- a/participants/models.py +++ b/participants/models.py @@ -1,12 +1,14 @@ from __future__ import unicode_literals from django.conf import settings +from django.core.validators import RegexValidator from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver from django.contrib.auth.models import PermissionsMixin from django.contrib.auth.base_user import AbstractBaseUser from django.utils.crypto import get_random_string +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from rest_framework.authtoken.models import Token from uuid import uuid4 @@ -14,12 +16,27 @@ from .managers import UserManager +@python_2_unicode_compatible +class Role(models.Model): + name = models.CharField(max_length=50) + + class Meta(object): + ordering = ['name'] + + def __str__(self): + return self.name + + class User(AbstractBaseUser, PermissionsMixin): email = models.EmailField(_('email address'), unique=True) first_name = models.CharField(_('first_name'), max_length=30, blank=True) last_name = models.CharField(_('last_name'), max_length=30, blank=True) full_name = models.CharField(max_length=255, blank=True, null=True, unique=True) date_joined = models.DateTimeField(_('date joined'), auto_now_add=True) + phone_regex = RegexValidator(regex=r'^\d{9}$', + message="Phone number must be entered in the format: '999999999'. Up to 9 digits allowed.") + phone_number = models.CharField(validators=[phone_regex], max_length=9, blank=True, null=True) + role = models.ForeignKey(Role, blank=True, null=True) is_staff = models.BooleanField(_('is staff'), default=False) is_jury = models.BooleanField(default=False) diff --git a/sample_data/role.json b/sample_data/role.json new file mode 100644 index 0000000..9a4502a --- /dev/null +++ b/sample_data/role.json @@ -0,0 +1,37 @@ +[{ + "model": "participants.role", + "pk": 1, + "fields": { + "name": "Backend Developer" + } +}, { + "model": "participants.role", + "pk": 2, + "fields": { + "name": "Frontend Developer" + } +}, { + "model": "participants.role", + "pk": 3, + "fields": { + "name": "Mobile Developer" + } +}, { + "model": "participants.role", + "pk": 4, + "fields": { + "name": "UX" + } +}, { + "model": "participants.role", + "pk": 5, + "fields": { + "name": "QA" + } +}, { + "model": "participants.role", + "pk": 6, + "fields": { + "name": "Fullstack Developer" + } +}] \ No newline at end of file diff --git a/sample_data/users.json b/sample_data/users.json index 24d756b..453f071 100644 --- a/sample_data/users.json +++ b/sample_data/users.json @@ -3,13 +3,15 @@ "pk": 1, "fields": { "password": "pbkdf2_sha256$30000$wck7Fjg9YlVJ$rMoCsMakvkI1oRmAlWCpvtNMmT6MVyOcrErcnaZWLOM=", - "last_login": "2018-05-08T18:21:39Z", + "last_login": "2018-05-11T01:10:41.421Z", "is_superuser": true, "email": "sinfante@belatrixsf.com", "first_name": "Sergio", "last_name": "Infante", "full_name": "Sergio Infante", "date_joined": "2018-04-13T03:28:41.467Z", + "phone_number": null, + "role": null, "is_staff": true, "is_jury": true, "is_moderator": true, @@ -33,6 +35,8 @@ "last_name": "Registrado", "full_name": "Participante Registrado", "date_joined": "2018-04-14T14:05:08.636Z", + "phone_number": null, + "role": null, "is_staff": false, "is_jury": false, "is_moderator": false, @@ -56,6 +60,8 @@ "last_name": "No participante", "full_name": "Usuario No participante", "date_joined": "2018-04-14T14:08:13.983Z", + "phone_number": null, + "role": null, "is_staff": false, "is_jury": false, "is_moderator": false, @@ -79,6 +85,8 @@ "last_name": "Hackatrix", "full_name": "Organizador Hackatrix", "date_joined": "2018-04-14T14:10:13.543Z", + "phone_number": null, + "role": null, "is_staff": true, "is_jury": false, "is_moderator": false, @@ -102,6 +110,8 @@ "last_name": "Hackatrix", "full_name": "Jurado Hackatrix", "date_joined": "2018-04-14T15:28:12.191Z", + "phone_number": null, + "role": null, "is_staff": false, "is_jury": true, "is_moderator": false, @@ -125,6 +135,8 @@ "last_name": "hackatrix", "full_name": "Developer Hackatrix", "date_joined": "2018-04-24T15:05:49.752Z", + "phone_number": "987654321", + "role": 3, "is_staff": true, "is_jury": false, "is_moderator": false, @@ -148,6 +160,8 @@ "last_name": "hackatrix", "full_name": "QA Hackatrix", "date_joined": "2018-04-24T15:07:03.261Z", + "phone_number": "987654333", + "role": 5, "is_staff": true, "is_jury": false, "is_moderator": false, @@ -171,6 +185,8 @@ "last_name": "Jurado", "full_name": "Segundo Jurado", "date_joined": "2018-04-29T05:16:38.638Z", + "phone_number": null, + "role": null, "is_staff": false, "is_jury": true, "is_moderator": false, @@ -194,6 +210,8 @@ "last_name": "Participante", "full_name": "Segundo Participante", "date_joined": "2018-04-29T05:17:34.685Z", + "phone_number": null, + "role": null, "is_staff": false, "is_jury": false, "is_moderator": false, @@ -217,6 +235,8 @@ "last_name": "", "full_name": "Diego Velasquez", "date_joined": "2018-05-08T18:24:29.061Z", + "phone_number": "", + "role": 3, "is_staff": true, "is_jury": false, "is_moderator": true, @@ -240,6 +260,8 @@ "last_name": "", "full_name": "Karla Cerron", "date_joined": "2018-05-08T18:25:19.632Z", + "phone_number": "", + "role": 4, "is_staff": true, "is_jury": true, "is_moderator": true, From 82dbcc35de0f6a4990be8b24a6b9b11998c80d89 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Thu, 10 May 2018 21:32:49 -0500 Subject: [PATCH 16/72] Add phone_number and role to user serializers. --- ideas/serializers.py | 5 ++++- participants/models.py | 5 +++-- participants/serializers.py | 16 ++++++++++++++-- participants/views.py | 13 +++++++++---- sample_data/users.json | 8 ++++---- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/ideas/serializers.py b/ideas/serializers.py index ecf4e36..d2999c8 100644 --- a/ideas/serializers.py +++ b/ideas/serializers.py @@ -1,14 +1,17 @@ from rest_framework import serializers from events.models import Event from participants.models import User +from participants.serializers import RoleSerializer from .models import Idea, IdeaParticipant, IdeaScores, IdeaScoresCriteria class UserSerializer(serializers.ModelSerializer): + role = RoleSerializer() + class Meta(object): model = User - fields = ('id', 'full_name', 'email') + fields = ('id', 'full_name', 'email', 'phone_number', 'role') class EventSerializer(serializers.ModelSerializer): diff --git a/participants/models.py b/participants/models.py index 2789665..93853de 100644 --- a/participants/models.py +++ b/participants/models.py @@ -33,8 +33,9 @@ class User(AbstractBaseUser, PermissionsMixin): last_name = models.CharField(_('last_name'), max_length=30, blank=True) full_name = models.CharField(max_length=255, blank=True, null=True, unique=True) date_joined = models.DateTimeField(_('date joined'), auto_now_add=True) - phone_regex = RegexValidator(regex=r'^\d{9}$', - message="Phone number must be entered in the format: '999999999'. Up to 9 digits allowed.") + phone_regex = RegexValidator( + regex=r'^\d{9}$', + message="Phone number must be entered in the format: '999999999'. Up to 9 digits allowed.") phone_number = models.CharField(validators=[phone_regex], max_length=9, blank=True, null=True) role = models.ForeignKey(Role, blank=True, null=True) diff --git a/participants/serializers.py b/participants/serializers.py index 65c374e..d2bf44e 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -1,13 +1,23 @@ from rest_framework import serializers -from .models import User +from .models import User, Role + + +class RoleSerializer(serializers.ModelSerializer): + class Meta(object): + model = Role + fields = '__all__' class UserSerializer(serializers.ModelSerializer): + role = RoleSerializer() + class Meta(object): model = User fields = ('id', 'email', 'full_name', + 'phone_number', + 'role', 'is_moderator', 'is_staff', 'is_active', @@ -19,8 +29,10 @@ class UserCreationSerializer(serializers.Serializer): email = serializers.CharField(max_length=50) -class UserFullnameSerializer(serializers.Serializer): +class UserProfileSerializer(serializers.Serializer): full_name = serializers.CharField(max_length=255) + phone_number = serializers.IntegerField() + role_id = serializers.IntegerField() class UserAuthenticationSerializer(serializers.Serializer): diff --git a/participants/views.py b/participants/views.py index 20a6f6f..59268b4 100644 --- a/participants/views.py +++ b/participants/views.py @@ -12,9 +12,9 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import StaticHTMLRenderer from rest_framework.response import Response -from .models import User, Participant +from .models import User, Participant, Role from .serializers import UserSerializer, UserCreationSerializer -from .serializers import UserUpdatePasswordSerializer, UserFullnameSerializer +from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer from events.models import Event, EventParticipant @@ -100,16 +100,21 @@ def user_update(request): Update user data --- PATCH: - serializer: participants.serializers.UserFullnameSerializer + serializer: participants.serializers.UserProfileSerializer response_serializer: participants.serializers.UserSerializer """ if request.method == 'PATCH': - serializer = UserFullnameSerializer(data=request.data) + serializer = UserProfileSerializer(data=request.data) if serializer.is_valid(raise_exception=True): full_name = serializer.validated_data['full_name'] + phone_number = serializer.validated_data['phone_number'] + role_id = serializer.validated_data['role_id'] + role = Role.objects.get(pk=role_id) current_user = request.user current_user.full_name = full_name + current_user.phone_number = phone_number + current_user.role = role current_user.save() serializer = UserSerializer(current_user) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) diff --git a/sample_data/users.json b/sample_data/users.json index 453f071..967d843 100644 --- a/sample_data/users.json +++ b/sample_data/users.json @@ -3,15 +3,15 @@ "pk": 1, "fields": { "password": "pbkdf2_sha256$30000$wck7Fjg9YlVJ$rMoCsMakvkI1oRmAlWCpvtNMmT6MVyOcrErcnaZWLOM=", - "last_login": "2018-05-11T01:10:41.421Z", + "last_login": "2018-05-11T01:10:41Z", "is_superuser": true, "email": "sinfante@belatrixsf.com", "first_name": "Sergio", "last_name": "Infante", "full_name": "Sergio Infante", "date_joined": "2018-04-13T03:28:41.467Z", - "phone_number": null, - "role": null, + "phone_number": "", + "role": 1, "is_staff": true, "is_jury": true, "is_moderator": true, @@ -136,7 +136,7 @@ "full_name": "Developer Hackatrix", "date_joined": "2018-04-24T15:05:49.752Z", "phone_number": "987654321", - "role": 3, + "role": 6, "is_staff": true, "is_jury": false, "is_moderator": false, From 44c7afca9997ba79efe73190fa61f9a751ea0388 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 11 May 2018 08:07:04 -0500 Subject: [PATCH 17/72] Add IdeaCandidate model and basic get list endpoint --- ideas/admin.py | 6 +++++- ideas/migrations/0001_initial.py | 16 +++++++++++++++- ideas/models.py | 15 +++++++++++++++ ideas/serializers.py | 10 +++++++++- ideas/urls.py | 4 +++- ideas/views.py | 26 ++++++++++++++++++++++++-- 6 files changed, 71 insertions(+), 6 deletions(-) diff --git a/ideas/admin.py b/ideas/admin.py index b6d5e0d..f44f4e3 100644 --- a/ideas/admin.py +++ b/ideas/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin from import_export.admin import ImportExportMixin -from .models import Idea, IdeaParticipant, IdeaVotes, IdeaScoresCriteria, IdeaScores +from .models import Idea, IdeaVotes, IdeaScoresCriteria, IdeaScores +from .models import IdeaParticipant, IdeaCandidate class IdeaAdmin(ImportExportMixin, admin.ModelAdmin): @@ -11,6 +12,8 @@ class IdeaAdmin(ImportExportMixin, admin.ModelAdmin): class IdeaParticipantAdmin(admin.ModelAdmin): list_display = ('user', 'idea') +class IdeaCandidateAdmin(admin.ModelAdmin): + list_display = ('user', 'idea', 'is_accepted') class IdeaVotesAdmin(admin.ModelAdmin): list_display = ('participant', 'idea', 'event') @@ -26,6 +29,7 @@ class IdeaScoresAdmin(ImportExportMixin, admin.ModelAdmin): admin.site.register(Idea, IdeaAdmin) admin.site.register(IdeaParticipant, IdeaParticipantAdmin) +admin.site.register(IdeaCandidate, IdeaCandidateAdmin) admin.site.register(IdeaVotes, IdeaVotesAdmin) admin.site.register(IdeaScoresCriteria, IdeaScoresCriteriaAdmin) admin.site.register(IdeaScores, IdeaScoresAdmin) diff --git a/ideas/migrations/0001_initial.py b/ideas/migrations/0001_initial.py index 91d4012..3e0801c 100644 --- a/ideas/migrations/0001_initial.py +++ b/ideas/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-10 02:56 +# Generated by Django 1.10.8 on 2018-05-11 11:54 from __future__ import unicode_literals from django.conf import settings @@ -32,6 +32,20 @@ class Migration(migrations.Migration): 'ordering': ['title'], }, ), + migrations.CreateModel( + name='IdeaCandidate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_accepted', models.BooleanField(default=False)), + ('idea', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ideas.Idea')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['idea'], + 'verbose_name': 'candidate', + 'verbose_name_plural': 'candidates', + }, + ), migrations.CreateModel( name='IdeaParticipant', fields=[ diff --git a/ideas/models.py b/ideas/models.py index 5570ba2..da0b690 100644 --- a/ideas/models.py +++ b/ideas/models.py @@ -35,6 +35,21 @@ class Meta(object): verbose_name_plural = 'groups' +@python_2_unicode_compatible +class IdeaCandidate(models.Model): + idea = models.ForeignKey(Idea) + user = models.ForeignKey('participants.User') + is_accepted = models.BooleanField(default=False) + + def __str__(self): + return self.idea.title + + class Meta(object): + ordering = ['idea'] + verbose_name = 'candidate' + verbose_name_plural = 'candidates' + + class IdeaVotes(models.Model): event = models.ForeignKey('events.Event') idea = models.ForeignKey(Idea) diff --git a/ideas/serializers.py b/ideas/serializers.py index d2999c8..647d52f 100644 --- a/ideas/serializers.py +++ b/ideas/serializers.py @@ -3,7 +3,7 @@ from participants.models import User from participants.serializers import RoleSerializer -from .models import Idea, IdeaParticipant, IdeaScores, IdeaScoresCriteria +from .models import Idea, IdeaCandidate, IdeaParticipant, IdeaScores, IdeaScoresCriteria class UserSerializer(serializers.ModelSerializer): @@ -51,6 +51,14 @@ class Meta(object): fields = ('user', ) +class IdeaCandidatesSerializer(serializers.ModelSerializer): + user = UserSerializer() + + class Meta(object): + model = IdeaCandidate + fields = ('user', ) + + class IdeaRegistrationSerializer(serializers.Serializer): user_id = serializers.IntegerField() diff --git a/ideas/urls.py b/ideas/urls.py index 63b51fd..bcf912b 100644 --- a/ideas/urls.py +++ b/ideas/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import url -from .views import idea, idea_create, idea_register, idea_participants, idea_unregister +from .views import idea, idea_create, idea_register, idea_unregister +from .views import idea_candidates, idea_participants from .views import idea_completed, idea_open, idea_rate, idea_scores_criteria from .views import idea_validate, idea_draft_list @@ -11,6 +12,7 @@ url(r'^(?P\d+)/register/$', idea_register, name='idea_register'), url(r'^(?P\d+)/unregister/$', idea_unregister, name='idea_unregister'), url(r'^(?P\d+)/validate/$', idea_validate, name='idea_validate'), + url(r'^(?P\d+)/candidates/$', idea_candidates, name='idea_candidates'), url(r'^(?P\d+)/participants/$', idea_participants, name='idea_participants'), url(r'^(?P\d+)/rate/$', idea_rate, name='idea_rate'), # url(r'^(?P\d+)/vote/$', idea_vote, name='idea_vote'), diff --git a/ideas/views.py b/ideas/views.py index ac77037..f19392d 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -11,8 +11,10 @@ from participants.models import User from participants.permissions import IsJury, IsModerator -from .models import Idea, IdeaParticipant, IdeaVotes, IdeaScores, IdeaScoresCriteria -from .serializers import IdeaCreationSerializer, IdeaSerializer, IdeaParticipantsSerializer +from .models import Idea, IdeaVotes, IdeaScores, IdeaScoresCriteria +from .models import IdeaCandidate, IdeaParticipant +from .serializers import IdeaCreationSerializer, IdeaSerializer +from .serializers import IdeaCandidatesSerializer, IdeaParticipantsSerializer from .serializers import IdeaRegistrationSerializer, IdeaVoteSerializer, IdeaSerializerWithVotes from .serializers import IdeaUpdateSerializer, IdeaScoreSerializer, IdeaScoreModelSerializer from .serializers import IdeaScoresCriteriaSerializer @@ -82,6 +84,26 @@ def idea_create(request): return Response(serializer.data, status=status.HTTP_201_CREATED) +@api_view(['GET']) +@permission_classes((IsAuthenticated, )) +def idea_candidates(request, idea_id): + """ + Endpoint to get candidate list group by idea + --- + GET: + serializer: ideas.serializers.IdeaCandidatesSerializer + """ + idea = get_object_or_404(Idea, pk=idea_id) + candidates = IdeaCandidate.objects.filter(idea=idea) + if len(IdeaCandidate.objects.filter(idea=idea, user=request.user)) > 0: + is_candidate = True + else: + is_candidate = False + serializer = IdeaCandidatesSerializer(candidates, many=True) + return Response({"is_candidate": is_candidate, + "candidates": serializer.data}, status=status.HTTP_200_OK) + + @api_view(['GET']) @permission_classes((IsAuthenticated, )) def idea_participants(request, idea_id): From e12e58fda320d535bba16c66eba612d6c231bafc Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 11 May 2018 08:24:35 -0500 Subject: [PATCH 18/72] Add endpoint to register a user as a candidate --- ideas/admin.py | 2 ++ ideas/migrations/0001_initial.py | 6 +++++- ideas/models.py | 1 + ideas/serializers.py | 4 ++++ ideas/urls.py | 4 +++- ideas/views.py | 27 ++++++++++++++++++++++++++- 6 files changed, 41 insertions(+), 3 deletions(-) diff --git a/ideas/admin.py b/ideas/admin.py index f44f4e3..1fb57b6 100644 --- a/ideas/admin.py +++ b/ideas/admin.py @@ -12,9 +12,11 @@ class IdeaAdmin(ImportExportMixin, admin.ModelAdmin): class IdeaParticipantAdmin(admin.ModelAdmin): list_display = ('user', 'idea') + class IdeaCandidateAdmin(admin.ModelAdmin): list_display = ('user', 'idea', 'is_accepted') + class IdeaVotesAdmin(admin.ModelAdmin): list_display = ('participant', 'idea', 'event') diff --git a/ideas/migrations/0001_initial.py b/ideas/migrations/0001_initial.py index 3e0801c..b60aa5b 100644 --- a/ideas/migrations/0001_initial.py +++ b/ideas/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-11 11:54 +# Generated by Django 1.10.8 on 2018-05-11 13:21 from __future__ import unicode_literals from django.conf import settings @@ -121,4 +121,8 @@ class Migration(migrations.Migration): name='ideaparticipant', unique_together=set([('idea', 'user')]), ), + migrations.AlterUniqueTogether( + name='ideacandidate', + unique_together=set([('idea', 'user')]), + ), ] diff --git a/ideas/models.py b/ideas/models.py index da0b690..8bb6ac7 100644 --- a/ideas/models.py +++ b/ideas/models.py @@ -46,6 +46,7 @@ def __str__(self): class Meta(object): ordering = ['idea'] + unique_together = ('idea', 'user') verbose_name = 'candidate' verbose_name_plural = 'candidates' diff --git a/ideas/serializers.py b/ideas/serializers.py index 647d52f..2a2f13d 100644 --- a/ideas/serializers.py +++ b/ideas/serializers.py @@ -59,6 +59,10 @@ class Meta(object): fields = ('user', ) +class IdeaCandidateRegistrationSerializer(serializers.Serializer): + user_id = serializers.IntegerField() + + class IdeaRegistrationSerializer(serializers.Serializer): user_id = serializers.IntegerField() diff --git a/ideas/urls.py b/ideas/urls.py index bcf912b..4e32cd8 100644 --- a/ideas/urls.py +++ b/ideas/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import url -from .views import idea, idea_create, idea_register, idea_unregister +from .views import idea, idea_create +from .views import idea_register_candidate, idea_register, idea_unregister from .views import idea_candidates, idea_participants from .views import idea_completed, idea_open, idea_rate, idea_scores_criteria from .views import idea_validate, idea_draft_list @@ -10,6 +11,7 @@ url(r'^(?P\d+)/completed/$', idea_completed, name='idea_completed'), url(r'^(?P\d+)/open/$', idea_open, name='idea_open'), url(r'^(?P\d+)/register/$', idea_register, name='idea_register'), + url(r'^(?P\d+)/register/candidate/$', idea_register_candidate, name='idea_register_candidate'), url(r'^(?P\d+)/unregister/$', idea_unregister, name='idea_unregister'), url(r'^(?P\d+)/validate/$', idea_validate, name='idea_validate'), url(r'^(?P\d+)/candidates/$', idea_candidates, name='idea_candidates'), diff --git a/ideas/views.py b/ideas/views.py index f19392d..c2d2348 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -15,7 +15,8 @@ from .models import IdeaCandidate, IdeaParticipant from .serializers import IdeaCreationSerializer, IdeaSerializer from .serializers import IdeaCandidatesSerializer, IdeaParticipantsSerializer -from .serializers import IdeaRegistrationSerializer, IdeaVoteSerializer, IdeaSerializerWithVotes +from .serializers import IdeaCandidateRegistrationSerializer, IdeaRegistrationSerializer +from .serializers import IdeaVoteSerializer, IdeaSerializerWithVotes from .serializers import IdeaUpdateSerializer, IdeaScoreSerializer, IdeaScoreModelSerializer from .serializers import IdeaScoresCriteriaSerializer @@ -124,6 +125,30 @@ def idea_participants(request, idea_id): "team_members": serializer.data}, status=status.HTTP_200_OK) +@api_view(['POST']) +@permission_classes((IsAuthenticated, )) +def idea_register_candidate(request, idea_id): + """ + Endpoint to register user as a candidate into an idea + --- + POST: + serializer: ideas.serializers.IdeaCandidateRegistrationSerializer + """ + serializer = IdeaCandidateRegistrationSerializer(data=request.data) + if serializer.is_valid(raise_exception=True): + idea = get_object_or_404(Idea, pk=idea_id) + user = get_object_or_404(User, pk=serializer.validated_data['user_id']) + try: + IdeaCandidate.objects.create(idea=idea, user=user) + except Exception as e: + print(e) + raise NotAcceptable("Ya se registró como candidato.") + candidates = IdeaCandidate.objects.filter(idea=idea) + serializer = IdeaCandidatesSerializer(candidates, many=True) + return Response({"is_candidate": True, + "candidates": serializer.data}, status=status.HTTP_201_CREATED) + + @api_view(['POST']) @permission_classes((IsAuthenticated, )) def idea_register(request, idea_id): From c3d31f90415ed3413438d3a2a61197dfc72ea692 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 11 May 2018 08:38:42 -0500 Subject: [PATCH 19/72] Add unregister as a candidate endpoint --- ideas/urls.py | 3 ++- ideas/views.py | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ideas/urls.py b/ideas/urls.py index 4e32cd8..1c06212 100644 --- a/ideas/urls.py +++ b/ideas/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url from .views import idea, idea_create -from .views import idea_register_candidate, idea_register, idea_unregister +from .views import idea_register_candidate, idea_register, idea_unregister, idea_unregister_candidate from .views import idea_candidates, idea_participants from .views import idea_completed, idea_open, idea_rate, idea_scores_criteria from .views import idea_validate, idea_draft_list @@ -13,6 +13,7 @@ url(r'^(?P\d+)/register/$', idea_register, name='idea_register'), url(r'^(?P\d+)/register/candidate/$', idea_register_candidate, name='idea_register_candidate'), url(r'^(?P\d+)/unregister/$', idea_unregister, name='idea_unregister'), + url(r'^(?P\d+)/unregister/candidate/$', idea_unregister_candidate, name='idea_unregister_candidate'), url(r'^(?P\d+)/validate/$', idea_validate, name='idea_validate'), url(r'^(?P\d+)/candidates/$', idea_candidates, name='idea_candidates'), url(r'^(?P\d+)/participants/$', idea_participants, name='idea_participants'), diff --git a/ideas/views.py b/ideas/views.py index c2d2348..4d393fa 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -188,11 +188,31 @@ def idea_register(request, idea_id): "team_members": serializer.data}, status=status.HTTP_201_CREATED) +@api_view(['POST']) +@permission_classes((IsAuthenticated, )) +def idea_unregister_candidate(request, idea_id): + """ + Endpoint to unregister user as a candidate into an idea + --- + POST: + serializer: ideas.serializers.IdeaCandidateRegistrationSerializer + """ + serializer = IdeaCandidateRegistrationSerializer(data=request.data) + if serializer.is_valid(raise_exception=True): + idea = get_object_or_404(Idea, pk=idea_id) + user = get_object_or_404(User, pk=serializer.validated_data['user_id']) + get_object_or_404(IdeaCandidate, idea=idea, user=user).delete() + candidates = IdeaCandidate.objects.filter(idea=idea) + serializer = IdeaCandidatesSerializer(candidates, many=True) + return Response({"is_candidate": False, + "candidates": serializer.data}, status=status.HTTP_202_ACCEPTED) + + @api_view(['POST']) @permission_classes((IsAuthenticated, )) def idea_unregister(request, idea_id): """ - Endpoint to register user into an idea + Endpoint to unregister user into an idea --- POST: serializer: ideas.serializers.IdeaRegistrationSerializer @@ -208,7 +228,7 @@ def idea_unregister(request, idea_id): participants = IdeaParticipant.objects.filter(idea=idea) serializer = IdeaParticipantsSerializer(participants, many=True) return Response({"is_registered": False, - "team_members": serializer.data}, status=status.HTTP_201_CREATED) + "team_members": serializer.data}, status=status.HTTP_202_ACCEPTED) @api_view(['PATCH']) From 954fc5eca711ed9109d1b65e635f76b729c73e72 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 11 May 2018 12:35:41 -0500 Subject: [PATCH 20/72] Add user list endpoint --- participants/urls.py | 3 ++- participants/views.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/participants/urls.py b/participants/urls.py index 09bf895..6bc7ce7 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -1,5 +1,5 @@ from django.conf.urls import url -from .views import user_profile, user_detail +from .views import user_profile, user_detail, user_list from .views import user_creation, user_update, user_update_password, CustomAuthToken from .views import user_password_recovery_request, user_password_recovery_confirmation, user_logout from ideas.views import my_ideas @@ -9,6 +9,7 @@ url(r'^(?P\d+)/$', user_detail, name='user_detail'), url(r'^authenticate/', CustomAuthToken.as_view()), url(r'^create/$', user_creation, name='user_creation'), + url(r'^list/$', user_list, name='user_list'), url(r'^logout/$', user_logout, name='user_logout'), url(r'^profile/$', user_profile, name='user_profile'), url(r'^ideas/$', my_ideas, name='my_ideas'), diff --git a/participants/views.py b/participants/views.py index 59268b4..8e319ef 100644 --- a/participants/views.py +++ b/participants/views.py @@ -12,7 +12,9 @@ from rest_framework.permissions import IsAuthenticated from rest_framework.renderers import StaticHTMLRenderer from rest_framework.response import Response + from .models import User, Participant, Role +from .permissions import IsModerator from .serializers import UserSerializer, UserCreationSerializer from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer from events.models import Event, EventParticipant @@ -32,6 +34,20 @@ def user_detail(request, user_id): return Response(serializer.data, status=status.HTTP_200_OK) +@api_view(['GET']) +@permission_classes((IsAuthenticated, IsModerator)) +def user_list(request): + """ + Returns user list + --- + GET: + response_serializer: participants.serializers.UserSerializer + """ + users = User.objects.filter(is_active=True) + serializer = UserSerializer(users, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + @api_view(['GET', ]) @permission_classes((IsAuthenticated, )) def user_profile(request): From 72c43fb9cbbe88eaf63361d00bcb5c3131df50b8 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 11 May 2018 13:03:00 -0500 Subject: [PATCH 21/72] Change validate to validation switch in order to have a canonical URL --- ideas/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ideas/urls.py b/ideas/urls.py index 1c06212..6de5a80 100644 --- a/ideas/urls.py +++ b/ideas/urls.py @@ -14,7 +14,7 @@ url(r'^(?P\d+)/register/candidate/$', idea_register_candidate, name='idea_register_candidate'), url(r'^(?P\d+)/unregister/$', idea_unregister, name='idea_unregister'), url(r'^(?P\d+)/unregister/candidate/$', idea_unregister_candidate, name='idea_unregister_candidate'), - url(r'^(?P\d+)/validate/$', idea_validate, name='idea_validate'), + url(r'^(?P\d+)/validation/switch/ $', idea_validate, name='idea_validate'), url(r'^(?P\d+)/candidates/$', idea_candidates, name='idea_candidates'), url(r'^(?P\d+)/participants/$', idea_participants, name='idea_participants'), url(r'^(?P\d+)/rate/$', idea_rate, name='idea_rate'), From 8bae342e3c0a8f8ebb142c265c43c1cff5a68643 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 11 May 2018 13:53:46 -0500 Subject: [PATCH 22/72] Add initial version of candidate approval endpoint --- ideas/urls.py | 5 +++-- ideas/views.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ideas/urls.py b/ideas/urls.py index 6de5a80..7e723c1 100644 --- a/ideas/urls.py +++ b/ideas/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from .views import idea, idea_create from .views import idea_register_candidate, idea_register, idea_unregister, idea_unregister_candidate -from .views import idea_candidates, idea_participants +from .views import idea_candidates, idea_participants, idea_candidate_approval from .views import idea_completed, idea_open, idea_rate, idea_scores_criteria from .views import idea_validate, idea_draft_list @@ -14,8 +14,9 @@ url(r'^(?P\d+)/register/candidate/$', idea_register_candidate, name='idea_register_candidate'), url(r'^(?P\d+)/unregister/$', idea_unregister, name='idea_unregister'), url(r'^(?P\d+)/unregister/candidate/$', idea_unregister_candidate, name='idea_unregister_candidate'), - url(r'^(?P\d+)/validation/switch/ $', idea_validate, name='idea_validate'), + url(r'^(?P\d+)/validation/switch/$', idea_validate, name='idea_validate'), url(r'^(?P\d+)/candidates/$', idea_candidates, name='idea_candidates'), + url(r'^(?P\d+)/candidate/approval/switch/$', idea_candidate_approval, name='idea_candidate_approval'), url(r'^(?P\d+)/participants/$', idea_participants, name='idea_participants'), url(r'^(?P\d+)/rate/$', idea_rate, name='idea_rate'), # url(r'^(?P\d+)/vote/$', idea_vote, name='idea_vote'), diff --git a/ideas/views.py b/ideas/views.py index 4d393fa..53b762c 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -188,6 +188,36 @@ def idea_register(request, idea_id): "team_members": serializer.data}, status=status.HTTP_201_CREATED) +@api_view(['POST']) +@permission_classes((IsAuthenticated, )) +def idea_candidate_approval(request, idea_id): + """ + Endpoint to accept candidate as a participant + --- + POST: + serializer: ideas.serializers.IdeaRegistrationSerializer + """ + idea = get_object_or_404(Idea, pk=idea_id) + if request.user == idea.author: + serializer = IdeaRegistrationSerializer(data=request.data) + if serializer.is_valid(raise_exception=True): + user = get_object_or_404(User, pk=serializer.validated_data['user_id']) + candidate = get_object_or_404(IdeaCandidate, idea=idea, user=user) + if candidate.is_accepted: + # TODO: flow to delete idea participant + candidate.is_accepted = False + else: + participants = IdeaParticipant.objects.filter(idea=idea) + if len(participants) < config.TEAM_MAX_SIZE: + IdeaParticipant.objects.create(idea=idea, user=user) + candidate.is_accepted = True + candidate.save() + # TODO: Flow to mark idea completed or not + return Response({'detail': 'eres el dueño'}, status=status.HTTP_202_ACCEPTED) + else: + raise NotAcceptable('No eres el autor de la idea.') + + @api_view(['POST']) @permission_classes((IsAuthenticated, )) def idea_unregister_candidate(request, idea_id): From 3f68ac7d7360c9d2322545042da0e1a59a995d1b Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 11 May 2018 21:34:28 -0500 Subject: [PATCH 23/72] Complete candidate approval endpoint --- ideas/views.py | 26 +++++++++++++++++++------ sample_data/idea_candidates.json | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 sample_data/idea_candidates.json diff --git a/ideas/views.py b/ideas/views.py index 53b762c..90b4100 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -204,16 +204,30 @@ def idea_candidate_approval(request, idea_id): user = get_object_or_404(User, pk=serializer.validated_data['user_id']) candidate = get_object_or_404(IdeaCandidate, idea=idea, user=user) if candidate.is_accepted: - # TODO: flow to delete idea participant + get_object_or_404(IdeaParticipant, idea=idea, user=user).delete() candidate.is_accepted = False else: participants = IdeaParticipant.objects.filter(idea=idea) - if len(participants) < config.TEAM_MAX_SIZE: - IdeaParticipant.objects.create(idea=idea, user=user) - candidate.is_accepted = True + if len(participants) < config.TEAM_MAX_SIZE and idea.is_completed is False: + try: + IdeaParticipant.objects.create(idea=idea, user=user) + candidate.is_accepted = True + except Exception as e: + print(e) + raise NotAcceptable("Ya se encuentra inscrito.") + else: + raise NotAcceptable("Alcanzó el número máximo por equipo.") candidate.save() - # TODO: Flow to mark idea completed or not - return Response({'detail': 'eres el dueño'}, status=status.HTTP_202_ACCEPTED) + + participants = IdeaParticipant.objects.filter(idea=idea) + if len(participants) == config.TEAM_MAX_SIZE: + idea.is_completed = True + else: + idea.is_completed = False + idea.save() + + serializer = IdeaParticipantsSerializer(participants, many=True) + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) else: raise NotAcceptable('No eres el autor de la idea.') diff --git a/sample_data/idea_candidates.json b/sample_data/idea_candidates.json new file mode 100644 index 0000000..76cc686 --- /dev/null +++ b/sample_data/idea_candidates.json @@ -0,0 +1,33 @@ +[{ + "model": "ideas.ideacandidate", + "pk": 1, + "fields": { + "idea": 1, + "user": 6, + "is_accepted": true + } +}, { + "model": "ideas.ideacandidate", + "pk": 2, + "fields": { + "idea": 1, + "user": 7, + "is_accepted": true + } +}, { + "model": "ideas.ideacandidate", + "pk": 3, + "fields": { + "idea": 1, + "user": 2, + "is_accepted": true + } +}, { + "model": "ideas.ideacandidate", + "pk": 4, + "fields": { + "idea": 1, + "user": 9, + "is_accepted": false + } +}] \ No newline at end of file From f0fef38dfae76f29a2ce525fad3f07076fae5be4 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 12 May 2018 20:56:31 -0500 Subject: [PATCH 24/72] Add meetings model in order to support future attendances --- BxEvents/settings/base.py | 2 +- events/admin.py | 7 ++++++- events/migrations/0001_initial.py | 17 ++++++++++++++++- events/models.py | 16 ++++++++++++++++ sample_data/meetings.json | 1 + 5 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 sample_data/meetings.json diff --git a/BxEvents/settings/base.py b/BxEvents/settings/base.py index 2363a8d..0bcf861 100644 --- a/BxEvents/settings/base.py +++ b/BxEvents/settings/base.py @@ -135,7 +135,7 @@ LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' +TIME_ZONE = 'America/Lima' USE_I18N = True diff --git a/events/admin.py b/events/admin.py index 4a3efe6..adffab4 100644 --- a/events/admin.py +++ b/events/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import City, Event, Interaction, Location, EventParticipant +from .models import City, Event, Interaction, Location, EventParticipant, Meeting class CityAdmin(admin.ModelAdmin): @@ -22,8 +22,13 @@ class LocationAdmin(admin.ModelAdmin): list_display = ('name', 'latitude', 'longitude') +class MeetingAdmin(admin.ModelAdmin): + list_display = ('name', 'start_date', 'end_date', 'event', 'is_over', 'is_active') + + admin.site.register(City, CityAdmin) admin.site.register(Event, EventAdmin) admin.site.register(EventParticipant, EventParticipantAdmin) admin.site.register(Interaction, InteractionAdmin) admin.site.register(Location, LocationAdmin) +admin.site.register(Meeting, MeetingAdmin) diff --git a/events/migrations/0001_initial.py b/events/migrations/0001_initial.py index bb24fa0..a58fc3e 100644 --- a/events/migrations/0001_initial.py +++ b/events/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-04-19 11:27 +# Generated by Django 1.10.8 on 2018-05-13 01:39 from __future__ import unicode_literals from django.conf import settings @@ -86,6 +86,21 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'locations', }, ), + migrations.CreateModel( + name='Meeting', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ('start_date', models.DateTimeField()), + ('end_date', models.DateTimeField()), + ('is_active', models.BooleanField(default=True)), + ('is_over', models.BooleanField(default=False)), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Event')), + ], + options={ + 'ordering': ['-start_date'], + }, + ), migrations.AddField( model_name='event', name='location', diff --git a/events/models.py b/events/models.py index c4d6cf4..518b5ba 100644 --- a/events/models.py +++ b/events/models.py @@ -89,3 +89,19 @@ class Meta(object): ordering = ['-pk'] verbose_name = 'interaction item' verbose_name_plural = 'interaction items' + + +@python_2_unicode_compatible +class Meeting(models.Model): + name = models.CharField(max_length=100, unique=True) + event = models.ForeignKey(Event) + start_date = models.DateTimeField() + end_date = models.DateTimeField() + is_active = models.BooleanField(default=True) + is_over = models.BooleanField(default=False) + + def __str__(self): + return self.name + + class Meta(object): + ordering = ['-start_date'] diff --git a/sample_data/meetings.json b/sample_data/meetings.json new file mode 100644 index 0000000..2ffb497 --- /dev/null +++ b/sample_data/meetings.json @@ -0,0 +1 @@ +[{"model": "events.meeting", "pk": 1, "fields": {"name": "Kick Off - Hackatrix 2018", "event": 19, "start_date": "2018-05-25T23:00:00Z", "end_date": "2018-05-26T01:00:00Z", "is_active": true, "is_over": false}}, {"model": "events.meeting", "pk": 2, "fields": {"name": "Full Day - Hackatrix 2018", "event": 19, "start_date": "2018-05-26T14:00:00Z", "end_date": "2018-05-27T01:00:00Z", "is_active": true, "is_over": false}}] \ No newline at end of file From 722f9c6b82c2772a43f11b7b58abcce16327a0c3 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sun, 13 May 2018 13:47:41 -0500 Subject: [PATCH 25/72] Add search optional parameter to user list endpoint --- participants/views.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/participants/views.py b/participants/views.py index 8e319ef..63b3195 100644 --- a/participants/views.py +++ b/participants/views.py @@ -2,6 +2,7 @@ from django.core.urlresolvers import reverse from django.contrib.auth import logout from django.contrib.sites.models import Site +from django.db.models import Q from django.shortcuts import get_object_or_404 from re import match as regex_match from rest_framework import status @@ -42,8 +43,24 @@ def user_list(request): --- GET: response_serializer: participants.serializers.UserSerializer + parameters: + - name: search + description: search terms + type: string + required: false + paramType: query """ users = User.objects.filter(is_active=True) + + if request.GET.get('search'): + + full_search_terms = request.GET.get('search') + search_terms_array = full_search_terms.split() + + if len(search_terms_array) > 0: + for term in search_terms_array: + users = users.filter(Q(full_name__icontains=term) | Q(email__icontains=term)) + serializer = UserSerializer(users, many=True) return Response(serializer.data, status=status.HTTP_200_OK) From c8344d807c1d44d78e91e4d21b04411e2170aab9 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sun, 13 May 2018 13:51:55 -0500 Subject: [PATCH 26/72] Add attendance model --- events/admin.py | 8 +++++++- events/migrations/0001_initial.py | 26 +++++++++++++++++++++++++- events/models.py | 10 ++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/events/admin.py b/events/admin.py index adffab4..d65d060 100644 --- a/events/admin.py +++ b/events/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from .models import City, Event, Interaction, Location, EventParticipant, Meeting +from .models import City, Event, Interaction, Location, EventParticipant +from .models import Meeting, Attendance class CityAdmin(admin.ModelAdmin): @@ -26,9 +27,14 @@ class MeetingAdmin(admin.ModelAdmin): list_display = ('name', 'start_date', 'end_date', 'event', 'is_over', 'is_active') +class AttendanceAdmin(admin.ModelAdmin): + list_display = ('datetime', 'meeting', 'participant') + + admin.site.register(City, CityAdmin) admin.site.register(Event, EventAdmin) admin.site.register(EventParticipant, EventParticipantAdmin) admin.site.register(Interaction, InteractionAdmin) admin.site.register(Location, LocationAdmin) admin.site.register(Meeting, MeetingAdmin) +admin.site.register(Attendance, AttendanceAdmin) diff --git a/events/migrations/0001_initial.py b/events/migrations/0001_initial.py index a58fc3e..63f68f1 100644 --- a/events/migrations/0001_initial.py +++ b/events/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-13 01:39 +# Generated by Django 1.10.8 on 2018-05-13 02:10 from __future__ import unicode_literals from django.conf import settings @@ -16,6 +16,16 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Attendance', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('datetime', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'ordering': ['-datetime'], + }, + ), migrations.CreateModel( name='City', fields=[ @@ -106,4 +116,18 @@ class Migration(migrations.Migration): name='location', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='events.Location'), ), + migrations.AddField( + model_name='attendance', + name='meeting', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.Meeting'), + ), + migrations.AddField( + model_name='attendance', + name='participant', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterUniqueTogether( + name='attendance', + unique_together=set([('meeting', 'participant')]), + ), ] diff --git a/events/models.py b/events/models.py index 518b5ba..2539226 100644 --- a/events/models.py +++ b/events/models.py @@ -105,3 +105,13 @@ def __str__(self): class Meta(object): ordering = ['-start_date'] + + +class Attendance(models.Model): + meeting = models.ForeignKey(Meeting) + participant = models.ForeignKey('participants.User') + datetime = models.DateTimeField(auto_now_add=True) + + class Meta(object): + unique_together = ('meeting', 'participant') + ordering = ["-datetime"] From 23aefa9fd845281d3a3557ae8d6608508d921701 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sun, 13 May 2018 20:06:36 -0500 Subject: [PATCH 27/72] Add meeting list endpoint --- events/serializers.py | 8 +++++++- events/urls.py | 2 ++ events/views.py | 24 +++++++++++++++++++++--- participants/permissions.py | 7 +++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/events/serializers.py b/events/serializers.py index 91750e0..1a05f7e 100644 --- a/events/serializers.py +++ b/events/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from .models import City, Event, Interaction +from .models import City, Event, Interaction, Meeting class CitySerializer(serializers.ModelSerializer): @@ -19,3 +19,9 @@ class InteractionSerializer(serializers.ModelSerializer): class Meta(object): model = Interaction fields = '__all__' + + +class MeetingSerializer(serializers.ModelSerializer): + class Meta(object): + model = Meeting + fields = '__all__' diff --git a/events/urls.py b/events/urls.py index fd6d76c..2ac4367 100644 --- a/events/urls.py +++ b/events/urls.py @@ -1,4 +1,5 @@ from .views import event_detail, event_list, event_upcoming_list, event_past_list, event_featured, event_city_list +from .views import meeting_list # from .views import event_interaction, event_interaction_vote from django.conf.urls import url from ideas.views import idea_list, idea_vote @@ -13,6 +14,7 @@ url(r'^featured/$', event_featured, name='event_featured'), # url(r'^interaction/(?P\d+)/vote$', event_interaction_vote, name='event_interaction_vote'), url(r'^list/$', event_list, name='event_list'), + url(r'^meeting/list/$', meeting_list, name='meeting_list'), url(r'^upcoming/list/$', event_upcoming_list, name='event_upcoming_list'), url(r'^past/list/$', event_past_list, name='event_past_list'), ] diff --git a/events/views.py b/events/views.py index f4088f6..5bf7e67 100644 --- a/events/views.py +++ b/events/views.py @@ -1,11 +1,15 @@ from django.shortcuts import get_object_or_404 from rest_framework import status -from rest_framework.decorators import api_view +from rest_framework.decorators import api_view, permission_classes from rest_framework.pagination import PageNumberPagination +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from utils.random_item import random_element_list -from .models import Event, Interaction, City -from .serializers import CitySerializer, EventSerializer, InteractionSerializer + +from participants.permissions import IsStaff + +from .models import Event, Interaction, City, Meeting +from .serializers import CitySerializer, EventSerializer, InteractionSerializer, MeetingSerializer @api_view(['GET', ]) @@ -208,3 +212,17 @@ def event_past_list(request): else: serializer = EventSerializer(events, many=True) return Response(serializer.data, status=status.HTTP_200_OK) + + +@api_view(['GET', ]) +@permission_classes((IsAuthenticated, IsStaff)) +def meeting_list(request): + """ + Returns meetings list to register attendance + --- + GET: + response_serializer: events.serializers.MeetingSerializer + """ + meetings = Meeting.objects.all().filter(is_active=True) + serializer = MeetingSerializer(meetings, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/participants/permissions.py b/participants/permissions.py index 64af083..6e51663 100644 --- a/participants/permissions.py +++ b/participants/permissions.py @@ -24,3 +24,10 @@ class IsModerator(permissions.BasePermission): def has_permission(self, request, view): return request.user and request.user.is_moderator + + +class IsStaff(permissions.BasePermission): + message = 'Staff restricted' + + def has_permission(self, request, view): + return request.user and request.user.is_staff From 4dd854150fe222f183674ff5aa31907d76746568 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sun, 13 May 2018 21:00:55 -0500 Subject: [PATCH 28/72] Add attendance endpoint --- events/serializers.py | 5 +++++ events/urls.py | 3 ++- events/views.py | 34 +++++++++++++++++++++++++++++++++- ideas/serializers.py | 8 ++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/events/serializers.py b/events/serializers.py index 1a05f7e..cf1565f 100644 --- a/events/serializers.py +++ b/events/serializers.py @@ -25,3 +25,8 @@ class MeetingSerializer(serializers.ModelSerializer): class Meta(object): model = Meeting fields = '__all__' + + +class AttendanceRegisterSerializer(serializers.Serializer): + meeting_id = serializers.IntegerField() + user_email = serializers.CharField() diff --git a/events/urls.py b/events/urls.py index 2ac4367..7526777 100644 --- a/events/urls.py +++ b/events/urls.py @@ -1,5 +1,5 @@ from .views import event_detail, event_list, event_upcoming_list, event_past_list, event_featured, event_city_list -from .views import meeting_list +from .views import meeting_list, register_attendance # from .views import event_interaction, event_interaction_vote from django.conf.urls import url from ideas.views import idea_list, idea_vote @@ -17,4 +17,5 @@ url(r'^meeting/list/$', meeting_list, name='meeting_list'), url(r'^upcoming/list/$', event_upcoming_list, name='event_upcoming_list'), url(r'^past/list/$', event_past_list, name='event_past_list'), + url(r'^register/attendance/$', register_attendance, name='register_attendance'), ] diff --git a/events/views.py b/events/views.py index 5bf7e67..3dcfca1 100644 --- a/events/views.py +++ b/events/views.py @@ -1,15 +1,21 @@ from django.shortcuts import get_object_or_404 from rest_framework import status from rest_framework.decorators import api_view, permission_classes +from rest_framework.exceptions import NotAcceptable from rest_framework.pagination import PageNumberPagination from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from utils.random_item import random_element_list +from ideas.models import IdeaParticipant +from ideas.serializers import IdeaParticipantsIdeasSerializer +from participants.models import User from participants.permissions import IsStaff +from participants.serializers import UserSerializer -from .models import Event, Interaction, City, Meeting +from .models import Event, Interaction, City, Meeting, Attendance from .serializers import CitySerializer, EventSerializer, InteractionSerializer, MeetingSerializer +from .serializers import AttendanceRegisterSerializer @api_view(['GET', ]) @@ -226,3 +232,29 @@ def meeting_list(request): meetings = Meeting.objects.all().filter(is_active=True) serializer = MeetingSerializer(meetings, many=True) return Response(serializer.data, status=status.HTTP_200_OK) + + +@api_view(['POST', ]) +@permission_classes((IsAuthenticated, IsStaff)) +def register_attendance(request): + """ + Register attendance to a meeting + --- + POST: + serializer: events.serializers.AttendanceRegisterSerializer + """ + serializer = AttendanceRegisterSerializer(data=request.data) + if serializer.is_valid(raise_exception=True): + meeting = get_object_or_404(Meeting, pk=serializer.validated_data['meeting_id']) + user = get_object_or_404(User, email=serializer.validated_data['user_email']) + try: + Attendance.objects.create(meeting=meeting, participant=user) + except Exception as e: + print(e) + raise NotAcceptable('Participante ya registrado.') + + idea_participant = IdeaParticipant.objects.filter(user=user) + user_serializer = UserSerializer(user) + idea_serializer = IdeaParticipantsIdeasSerializer(idea_participant, many=True) + return Response({'user': user_serializer.data, + 'ideas': idea_serializer.data}, status=status.HTTP_200_OK) diff --git a/ideas/serializers.py b/ideas/serializers.py index 2a2f13d..e32cee4 100644 --- a/ideas/serializers.py +++ b/ideas/serializers.py @@ -51,6 +51,14 @@ class Meta(object): fields = ('user', ) +class IdeaParticipantsIdeasSerializer(serializers.ModelSerializer): + idea = SimpleIdeaSerializer() + + class Meta(object): + model = IdeaParticipant + fields = ('idea', ) + + class IdeaCandidatesSerializer(serializers.ModelSerializer): user = UserSerializer() From 31385ba5504d790f8fa4dabc5b3e4c7139b1230c Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Mon, 14 May 2018 09:05:41 -0500 Subject: [PATCH 29/72] Change idea title max_length to 100 characters --- ideas/migrations/0001_initial.py | 4 +- ideas/models.py | 2 +- sample_data/ideas.json | 124 +++++++++++++++---------------- sample_data/meetings.json | 24 +++++- 4 files changed, 88 insertions(+), 66 deletions(-) diff --git a/ideas/migrations/0001_initial.py b/ideas/migrations/0001_initial.py index b60aa5b..4b6ba12 100644 --- a/ideas/migrations/0001_initial.py +++ b/ideas/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.8 on 2018-05-11 13:21 +# Generated by Django 1.10.8 on 2018-05-14 14:02 from __future__ import unicode_literals from django.conf import settings @@ -21,7 +21,7 @@ class Migration(migrations.Migration): name='Idea', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(max_length=255, unique=True)), + ('title', models.CharField(max_length=100, unique=True)), ('description', models.TextField(blank=True, null=True)), ('is_completed', models.BooleanField(default=False)), ('is_valid', models.BooleanField(default=False)), diff --git a/ideas/models.py b/ideas/models.py index 8bb6ac7..86b8f11 100644 --- a/ideas/models.py +++ b/ideas/models.py @@ -6,7 +6,7 @@ @python_2_unicode_compatible class Idea(models.Model): - title = models.CharField(max_length=255, unique=True) + title = models.CharField(max_length=100, unique=True) description = models.TextField(blank=True, null=True) author = models.ForeignKey('participants.User') event = models.ForeignKey('events.Event') diff --git a/sample_data/ideas.json b/sample_data/ideas.json index 0469dee..6e7713a 100644 --- a/sample_data/ideas.json +++ b/sample_data/ideas.json @@ -2,8 +2,8 @@ "model": "ideas.idea", "pk": 1, "fields": { - "title": "Agro IoT - Sistema en arduino para ver cultivos en tiempo real", - "description": "", + "title": "Agro IoT", + "description": "Sistema en arduino para ver cultivos en tiempo real", "author": 1, "event": 19, "is_completed": false, @@ -13,8 +13,8 @@ "model": "ideas.idea", "pk": 2, "fields": { - "title": "Auto Check - App para obtener informaci\u00f3n del veh\u00edculo", - "description": "", + "title": "Auto Check", + "description": "App para obtener informaci\u00f3n del veh\u00edculo", "author": 2, "event": 19, "is_completed": false, @@ -24,8 +24,8 @@ "model": "ideas.idea", "pk": 3, "fields": { - "title": "Encuentrame.pe - B\u00fasqueda de personas por medio de reconocimiento facial", - "description": "", + "title": "Encuentrame.pe", + "description": "B\u00fasqueda de personas por medio de reconocimiento facial", "author": 4, "event": 19, "is_completed": false, @@ -35,8 +35,8 @@ "model": "ideas.idea", "pk": 4, "fields": { - "title": "Social Faith - Sistema para promocionar campa\u00f1as de donaciones para ONG", - "description": "", + "title": "Social Faith", + "description": "Sistema para promocionar campa\u00f1as de donaciones para ONG", "author": 1, "event": 19, "is_completed": false, @@ -46,8 +46,8 @@ "model": "ideas.idea", "pk": 5, "fields": { - "title": "Entiende.pe - Plataforma para aprender de forma inteligente usando inteligencia artificial", - "description": "", + "title": "Entiende.pe", + "description": "Plataforma para aprender de forma inteligente usando inteligencia artificial", "author": 2, "event": 19, "is_completed": false, @@ -57,8 +57,8 @@ "model": "ideas.idea", "pk": 6, "fields": { - "title": "Ayudame.pe - Plataforma que sirve como agente de cambio para mejorar la calidad de vida de personas", - "description": "", + "title": "Ayudame.pe", + "description": "Plataforma que sirve como agente de cambio para mejorar la calidad de vida de personas", "author": 4, "event": 19, "is_completed": false, @@ -68,8 +68,8 @@ "model": "ideas.idea", "pk": 7, "fields": { - "title": "Maiden Assistant - App que ofrece recomendaciones basados en la experiencia de la persona", - "description": "", + "title": "Maiden Assistant", + "description": "App que ofrece recomendaciones basados en la experiencia de la persona", "author": 1, "event": 19, "is_completed": false, @@ -79,8 +79,8 @@ "model": "ideas.idea", "pk": 8, "fields": { - "title": "Estudia Por Gusto - App que ayuda a intercambiar conocimiento de forma interactiva", - "description": "", + "title": "Estudia Por Gusto", + "description": "App que ayuda a intercambiar conocimiento de forma interactiva", "author": 2, "event": 19, "is_completed": false, @@ -90,8 +90,8 @@ "model": "ideas.idea", "pk": 9, "fields": { - "title": "Blood care - Plataforma para encontrar una red de donantes de sangre voluntarios", - "description": "", + "title": "Blood care", + "description": "Plataforma para encontrar una red de donantes de sangre voluntarios", "author": 4, "event": 19, "is_completed": false, @@ -101,8 +101,8 @@ "model": "ideas.idea", "pk": 10, "fields": { - "title": "Trappist - Plataforma que nos ayuda a optimizar el tiempo de espera en los bancos usando sensores", - "description": "", + "title": "Trappist", + "description": "Plataforma que nos ayuda a optimizar el tiempo de espera en los bancos usando sensores", "author": 1, "event": 19, "is_completed": false, @@ -112,8 +112,8 @@ "model": "ideas.idea", "pk": 11, "fields": { - "title": "Ayllu FX - App para conectar y notificar personas para intercambiar divisas", - "description": "", + "title": "Ayllu FX", + "description": "App para conectar y notificar personas para intercambiar divisas", "author": 2, "event": 19, "is_completed": false, @@ -123,8 +123,8 @@ "model": "ideas.idea", "pk": 12, "fields": { - "title": "I do it - Plataforma para conectar personas que requieren servicios de utilidad", - "description": "", + "title": "I do it", + "description": "Plataforma para conectar personas que requieren servicios de utilidad", "author": 4, "event": 19, "is_completed": false, @@ -145,8 +145,8 @@ "model": "ideas.idea", "pk": 14, "fields": { - "title": "YoTeAyudo - App para voluntarios con el objetivo de ayudar y colaborar con los dem\u00e1s", - "description": "", + "title": "YoTeAyudo", + "description": "App para voluntarios con el objetivo de ayudar y colaborar con los dem\u00e1s", "author": 2, "event": 19, "is_completed": false, @@ -156,8 +156,8 @@ "model": "ideas.idea", "pk": 15, "fields": { - "title": "Parqueate - App para buscar zonas de estacionamiento", - "description": "", + "title": "Parqueate", + "description": "App para buscar zonas de estacionamiento", "author": 4, "event": 19, "is_completed": false, @@ -167,8 +167,8 @@ "model": "ideas.idea", "pk": 16, "fields": { - "title": "ReciclaPe - App m\u00f3vil para mejorar el proceso de reciclaje", - "description": "", + "title": "ReciclaPe", + "description": "App m\u00f3vil para mejorar el proceso de reciclaje", "author": 1, "event": 19, "is_completed": false, @@ -178,8 +178,8 @@ "model": "ideas.idea", "pk": 17, "fields": { - "title": "EcoFriends - App de retos ecol\u00f3gicos para minimizar el impacto con la naturaleza", - "description": "", + "title": "EcoFriends", + "description": "App de retos ecol\u00f3gicos para minimizar el impacto con la naturaleza", "author": 2, "event": 19, "is_completed": false, @@ -189,8 +189,8 @@ "model": "ideas.idea", "pk": 18, "fields": { - "title": "Co-Aprendamos.pe - Retos para aprender", - "description": "", + "title": "Co-Aprendamos.pe", + "description": "Retos para aprender", "author": 4, "event": 19, "is_completed": false, @@ -200,8 +200,8 @@ "model": "ideas.idea", "pk": 19, "fields": { - "title": "Smart Cacao - IOT aplicado a la agricultura, automatizar parcelas agricolas", - "description": "", + "title": "Smart Cacao", + "description": "IOT aplicado a la agricultura, automatizar parcelas agricolas", "author": 1, "event": 19, "is_completed": false, @@ -211,8 +211,8 @@ "model": "ideas.idea", "pk": 20, "fields": { - "title": "You Study - Comunidad de aprendizaje a trav\u00e9s de grupos de estudio", - "description": "", + "title": "You Study", + "description": "Comunidad de aprendizaje a trav\u00e9s de grupos de estudio", "author": 2, "event": 19, "is_completed": false, @@ -222,8 +222,8 @@ "model": "ideas.idea", "pk": 21, "fields": { - "title": "Regala una Sonrisa - Plataforma que gestiona donaciones de los usuarios para distribuir a ONG", - "description": "", + "title": "Regala una Sonrisa", + "description": "Plataforma que gestiona donaciones de los usuarios para distribuir a ONG", "author": 4, "event": 19, "is_completed": false, @@ -233,8 +233,8 @@ "model": "ideas.idea", "pk": 22, "fields": { - "title": "YoTambien - Sistema de capacitaci\u00f3n para personas con discapacidades", - "description": "", + "title": "YoTambien", + "description": "Sistema de capacitaci\u00f3n para personas con discapacidades", "author": 1, "event": 19, "is_completed": false, @@ -244,8 +244,8 @@ "model": "ideas.idea", "pk": 23, "fields": { - "title": "Hogar SOS - Marketplace de servicios para el hogar, como carpinter\u00eda, gasfiter\u00eda, entre otros", - "description": "", + "title": "Hogar SOS", + "description": "Marketplace de servicios para el hogar, como carpinter\u00eda, gasfiter\u00eda, entre otros", "author": 2, "event": 19, "is_completed": false, @@ -255,8 +255,8 @@ "model": "ideas.idea", "pk": 24, "fields": { - "title": "iChancon - Red social para colaborar en la resoluci\u00f3n de problemas acad\u00e9micos", - "description": "", + "title": "iChancon", + "description": "Red social para colaborar en la resoluci\u00f3n de problemas acad\u00e9micos", "author": 4, "event": 19, "is_completed": false, @@ -266,8 +266,8 @@ "model": "ideas.idea", "pk": 25, "fields": { - "title": "iMetrics - Medidor de agua inteligente conectado a una red neural para optimizar facturaciones", - "description": "", + "title": "iMetrics", + "description": "Medidor de agua inteligente conectado a una red neural para optimizar facturaciones", "author": 1, "event": 19, "is_completed": false, @@ -277,8 +277,8 @@ "model": "ideas.idea", "pk": 26, "fields": { - "title": "Mi Primer App - Participaci\u00f3n en proyectos para estudiantes de postgrado", - "description": "", + "title": "Mi Primer App", + "description": "Participaci\u00f3n en proyectos para estudiantes de postgrado", "author": 2, "event": 19, "is_completed": false, @@ -288,8 +288,8 @@ "model": "ideas.idea", "pk": 27, "fields": { - "title": "B\u00fasqueda de Personas Perdidas - Plataforma para buscar personas por medio de reconocimiento facial", - "description": "", + "title": "B\u00fasqueda de Personas Perdidas", + "description": "Plataforma para buscar personas por medio de reconocimiento facial", "author": 4, "event": 19, "is_completed": false, @@ -299,8 +299,8 @@ "model": "ideas.idea", "pk": 28, "fields": { - "title": "Amor a Sofia - App para ayuda a los postulantes a universidades", - "description": "", + "title": "Amor a Sofia", + "description": "App para ayuda a los postulantes a universidades", "author": 1, "event": 19, "is_completed": false, @@ -310,8 +310,8 @@ "model": "ideas.idea", "pk": 29, "fields": { - "title": "Medical Chain - Historiales m\u00e9dicos registrados en una red blockchain", - "description": "", + "title": "Medical Chain", + "description": "Historiales m\u00e9dicos registrados en una red blockchain", "author": 2, "event": 19, "is_completed": false, @@ -321,8 +321,8 @@ "model": "ideas.idea", "pk": 30, "fields": { - "title": "Gento - Plataforma que cambia la forma de como compramos ropa por medio de machine learning", - "description": "", + "title": "Gento", + "description": "Plataforma que cambia la forma de como compramos ropa por medio de machine learning", "author": 4, "event": 19, "is_completed": false, @@ -332,8 +332,8 @@ "model": "ideas.idea", "pk": 31, "fields": { - "title": "Sportify - App social para buscar gente que quiera jugar y este cerca de ti", - "description": "", + "title": "Sportify", + "description": "App social para buscar gente que quiera jugar y este cerca de ti", "author": 1, "event": 19, "is_completed": false, @@ -343,8 +343,8 @@ "model": "ideas.idea", "pk": 32, "fields": { - "title": "Dale Go - App social de eventos deportivos", - "description": "", + "title": "Dale Go", + "description": "App social de eventos deportivos", "author": 2, "event": 19, "is_completed": false, diff --git a/sample_data/meetings.json b/sample_data/meetings.json index 2ffb497..49a75a7 100644 --- a/sample_data/meetings.json +++ b/sample_data/meetings.json @@ -1 +1,23 @@ -[{"model": "events.meeting", "pk": 1, "fields": {"name": "Kick Off - Hackatrix 2018", "event": 19, "start_date": "2018-05-25T23:00:00Z", "end_date": "2018-05-26T01:00:00Z", "is_active": true, "is_over": false}}, {"model": "events.meeting", "pk": 2, "fields": {"name": "Full Day - Hackatrix 2018", "event": 19, "start_date": "2018-05-26T14:00:00Z", "end_date": "2018-05-27T01:00:00Z", "is_active": true, "is_over": false}}] \ No newline at end of file +[{ + "model": "events.meeting", + "pk": 1, + "fields": { + "name": "Kick Off - Hackatrix 2018", + "event": 19, + "start_date": "2018-05-25T23:00:00Z", + "end_date": "2018-05-26T01:00:00Z", + "is_active": true, + "is_over": false + } +}, { + "model": "events.meeting", + "pk": 2, + "fields": { + "name": "Full Day - Hackatrix 2018", + "event": 19, + "start_date": "2018-05-26T14:00:00Z", + "end_date": "2018-05-27T01:00:00Z", + "is_active": true, + "is_over": false + } +}] \ No newline at end of file From 34672c90a6d6666a686efb0c5c940366f5d0d7f2 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Mon, 14 May 2018 10:35:52 -0500 Subject: [PATCH 30/72] Add moderator, active and blocked flags to authentication response --- participants/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/participants/views.py b/participants/views.py index 63b3195..2feece3 100644 --- a/participants/views.py +++ b/participants/views.py @@ -263,6 +263,9 @@ def post(self, request, *args, **kwargs): 'email': user.email, 'is_staff': user.is_staff, 'is_jury': user.is_jury, + 'is_moderator': user.is_moderator, + 'is_active': user.is_active, + 'is_blocked': user.is_blocked, 'is_password_reset_required': user.is_password_reset_required, }) From 89dd019016045b86dbabe27c0648e7a48ceddab1 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Mon, 14 May 2018 12:35:31 -0500 Subject: [PATCH 31/72] Change idea participants permissions in order to allow guest users --- ideas/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ideas/views.py b/ideas/views.py index 90b4100..ac3ada7 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -106,7 +106,7 @@ def idea_candidates(request, idea_id): @api_view(['GET']) -@permission_classes((IsAuthenticated, )) +@permission_classes((IsAuthenticatedOrReadOnly, )) def idea_participants(request, idea_id): """ Endpoint to get participant list group by idea From 1121faf69c102aad0b6a46e642217cdc31d87a57 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Mon, 14 May 2018 14:32:54 -0500 Subject: [PATCH 32/72] Support anonymous user in idea participants request --- ideas/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ideas/views.py b/ideas/views.py index ac3ada7..e34ed92 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -116,10 +116,13 @@ def idea_participants(request, idea_id): """ idea = get_object_or_404(Idea, pk=idea_id) participants = IdeaParticipant.objects.filter(idea=idea) - if len(IdeaParticipant.objects.filter(idea=idea, user=request.user)) > 0: - is_registered = True - else: + if request.user.is_anonymous: is_registered = False + else: + if len(IdeaParticipant.objects.filter(idea=idea, user=request.user)) > 0: + is_registered = True + else: + is_registered = False serializer = IdeaParticipantsSerializer(participants, many=True) return Response({"is_registered": is_registered, "team_members": serializer.data}, status=status.HTTP_200_OK) From 33a09f8984afeaa9ef13a34230e2a9a41317f360 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Tue, 15 May 2018 13:25:42 -0500 Subject: [PATCH 33/72] Get full user information in profile view --- participants/views.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/participants/views.py b/participants/views.py index 2feece3..c9f549a 100644 --- a/participants/views.py +++ b/participants/views.py @@ -18,7 +18,9 @@ from .permissions import IsModerator from .serializers import UserSerializer, UserCreationSerializer from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer -from events.models import Event, EventParticipant + +from events.models import Event, EventParticipant, Attendance +from ideas.models import IdeaCandidate, IdeaParticipant, Idea @api_view(['GET', ]) @@ -74,7 +76,15 @@ def user_profile(request): GET: response_serializer: participants.serializers.UserSerializer """ - serializer = UserSerializer(request.user) + user = request.user + + events = EventParticipant.objects.filter(participant=user) + candidate_ideas = IdeaCandidate.objects.filter(user=user) + participant_ideas = IdeaParticipant.objects.filter(user=user) + attendances = Attendance.objects.filter(participant=user) + author_ideas = Idea.objects.filter(author=user) + serializer = UserSerializer(user) + return Response(serializer.data, status=status.HTTP_200_OK) From d73cadf12880c88184acfb7d3f53308ee525f028 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Tue, 15 May 2018 13:31:56 -0500 Subject: [PATCH 34/72] Add user roles endpoint --- participants/urls.py | 3 ++- participants/views.py | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/participants/urls.py b/participants/urls.py index 6bc7ce7..9071907 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from .views import user_profile, user_detail, user_list from .views import user_creation, user_update, user_update_password, CustomAuthToken -from .views import user_password_recovery_request, user_password_recovery_confirmation, user_logout +from .views import user_password_recovery_request, user_password_recovery_confirmation, user_logout, user_roles from ideas.views import my_ideas @@ -14,6 +14,7 @@ url(r'^profile/$', user_profile, name='user_profile'), url(r'^ideas/$', my_ideas, name='my_ideas'), url(r'^recover/$', user_password_recovery_request, name='user_password_recovery_request'), + url(r'^role/list/$', user_roles, name='user_roles'), url(r'^update/$', user_update, name='user_update'), url(r'^update/password/$', user_update_password, name='user_update_password'), url(r'^recover/(?P[0-9a-z-]+)$', diff --git a/participants/views.py b/participants/views.py index c9f549a..6df722f 100644 --- a/participants/views.py +++ b/participants/views.py @@ -17,7 +17,7 @@ from .models import User, Participant, Role from .permissions import IsModerator from .serializers import UserSerializer, UserCreationSerializer -from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer +from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer, RoleSerializer from events.models import Event, EventParticipant, Attendance from ideas.models import IdeaCandidate, IdeaParticipant, Idea @@ -84,7 +84,7 @@ def user_profile(request): attendances = Attendance.objects.filter(participant=user) author_ideas = Idea.objects.filter(author=user) serializer = UserSerializer(user) - + return Response(serializer.data, status=status.HTTP_200_OK) @@ -254,6 +254,20 @@ def user_password_recovery_confirmation(request, user_uuid): return Response(data) +@api_view(['GET', ]) +@permission_classes((IsAuthenticated, )) +def user_roles(request): + """ + Return user roles + --- + GET: + response_serializer: participants.serializers.RoleSerializer + """ + roles = Role.objects.all() + serializer = RoleSerializer(roles, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + class CustomAuthToken(ObtainAuthToken): def post(self, request, *args, **kwargs): """ From b53a207a88f6ac6f188d7e4cae9d574d27678865 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Tue, 15 May 2018 14:49:45 -0500 Subject: [PATCH 35/72] Change profile response --- participants/serializers.py | 16 ++++++++++++++++ participants/views.py | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/participants/serializers.py b/participants/serializers.py index d2bf44e..ccd1de9 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -1,6 +1,8 @@ from rest_framework import serializers from .models import User, Role +from events.models import Event, EventParticipant + class RoleSerializer(serializers.ModelSerializer): class Meta(object): @@ -52,3 +54,17 @@ class UserAuthenticationResponseSerializer(serializers.Serializer): class UserUpdatePasswordSerializer(serializers.Serializer): current_password = serializers.CharField(max_length=50) new_password = serializers.CharField(max_length=50) + + +class EventSerializer(serializers.ModelSerializer): + class Meta(object): + model = Event + fields = ('id', 'title') + + +class EventProfileSerializer(serializers.ModelSerializer): + event = EventSerializer() + + class Meta(object): + model = EventParticipant + fields = ('event',) diff --git a/participants/views.py b/participants/views.py index 6df722f..1ba1f22 100644 --- a/participants/views.py +++ b/participants/views.py @@ -18,6 +18,7 @@ from .permissions import IsModerator from .serializers import UserSerializer, UserCreationSerializer from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer, RoleSerializer +from .serializers import EventProfileSerializer from events.models import Event, EventParticipant, Attendance from ideas.models import IdeaCandidate, IdeaParticipant, Idea @@ -79,13 +80,15 @@ def user_profile(request): user = request.user events = EventParticipant.objects.filter(participant=user) + events_serializer = EventProfileSerializer(events, many=True) candidate_ideas = IdeaCandidate.objects.filter(user=user) participant_ideas = IdeaParticipant.objects.filter(user=user) attendances = Attendance.objects.filter(participant=user) author_ideas = Idea.objects.filter(author=user) serializer = UserSerializer(user) - return Response(serializer.data, status=status.HTTP_200_OK) + return Response({"user": serializer.data, + "events": events_serializer.data}, status=status.HTTP_200_OK) @api_view(['POST', ]) From 35dd4fe0cd642d22ba72ddfa9889f7710813af8a Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Tue, 15 May 2018 15:38:04 -0500 Subject: [PATCH 36/72] Complete profile response --- participants/serializers.py | 31 +++++++++++++++++++++++++++---- participants/views.py | 13 +++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/participants/serializers.py b/participants/serializers.py index ccd1de9..4bfaf4e 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -1,7 +1,8 @@ from rest_framework import serializers from .models import User, Role -from events.models import Event, EventParticipant +from events.models import Event, Meeting +from ideas.models import Idea class RoleSerializer(serializers.ModelSerializer): @@ -62,9 +63,31 @@ class Meta(object): fields = ('id', 'title') -class EventProfileSerializer(serializers.ModelSerializer): +class EventProfileSerializer(serializers.Serializer): event = EventSerializer() + +class IdeaSerializer(serializers.ModelSerializer): + class Meta(object): + model = Idea + fields = ('id', 'title') + + +class IdeaProfileSerializer(serializers.Serializer): + idea = IdeaSerializer() + + +class AuthorProfileSerializer(serializers.ModelSerializer): class Meta(object): - model = EventParticipant - fields = ('event',) + model = Idea + fields = ('id', 'title') + + +class MeetingSerializer(serializers.ModelSerializer): + class Meta(object): + model = Meeting + fields = ('id', 'name') + + +class AttendanceProfileSerializer(serializers.Serializer): + meeting = MeetingSerializer() diff --git a/participants/views.py b/participants/views.py index 1ba1f22..d7eacca 100644 --- a/participants/views.py +++ b/participants/views.py @@ -18,7 +18,8 @@ from .permissions import IsModerator from .serializers import UserSerializer, UserCreationSerializer from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer, RoleSerializer -from .serializers import EventProfileSerializer +from .serializers import EventProfileSerializer, IdeaProfileSerializer, AttendanceProfileSerializer +from .serializers import AuthorProfileSerializer from events.models import Event, EventParticipant, Attendance from ideas.models import IdeaCandidate, IdeaParticipant, Idea @@ -82,13 +83,21 @@ def user_profile(request): events = EventParticipant.objects.filter(participant=user) events_serializer = EventProfileSerializer(events, many=True) candidate_ideas = IdeaCandidate.objects.filter(user=user) + candidate_serializer = IdeaProfileSerializer(candidate_ideas, many=True) participant_ideas = IdeaParticipant.objects.filter(user=user) + participant_serializer = IdeaProfileSerializer(participant_ideas, many=True) attendances = Attendance.objects.filter(participant=user) + attendance_serializer = AttendanceProfileSerializer(attendances, many=True) author_ideas = Idea.objects.filter(author=user) + author_serializer = AuthorProfileSerializer(author_ideas, many=True) serializer = UserSerializer(user) return Response({"user": serializer.data, - "events": events_serializer.data}, status=status.HTTP_200_OK) + "events": events_serializer.data, + "candidate": candidate_serializer.data, + "participant": participant_serializer.data, + "author": author_serializer.data, + "attendance": attendance_serializer.data}, status=status.HTTP_200_OK) @api_view(['POST', ]) From e500f9655999a0d604016834bb2b254c6e9af73d Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Thu, 17 May 2018 09:54:11 -0500 Subject: [PATCH 37/72] Change is_accepted to False for candidate when participant is unregister --- ideas/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ideas/views.py b/ideas/views.py index e34ed92..a4660a1 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -269,6 +269,11 @@ def idea_unregister(request, idea_id): idea = get_object_or_404(Idea, pk=idea_id) user = get_object_or_404(User, pk=serializer.validated_data['user_id']) get_object_or_404(IdeaParticipant, idea=idea, user=user).delete() + + candidate = get_object_or_404(IdeaCandidate, idea=idea, user=user) + candidate.is_accepted = False + candidate.save() + if IdeaParticipant.objects.filter(idea=idea).count() < config.TEAM_MIN_SIZE: idea.is_completed = False idea.save() From 2778438a99fdac9c456ce9384b384d721a09013c Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Thu, 17 May 2018 12:27:03 -0500 Subject: [PATCH 38/72] Add sample data to test more test scenarios --- sample_data/ideas.json | 240 ++++++++++++++++++++++++------- sample_data/role.json | 12 ++ sample_data/users.json | 310 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 503 insertions(+), 59 deletions(-) diff --git a/sample_data/ideas.json b/sample_data/ideas.json index 6e7713a..228d1c0 100644 --- a/sample_data/ideas.json +++ b/sample_data/ideas.json @@ -4,10 +4,10 @@ "fields": { "title": "Agro IoT", "description": "Sistema en arduino para ver cultivos en tiempo real", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, - "is_valid": false + "is_valid": true } }, { "model": "ideas.idea", @@ -15,8 +15,8 @@ "fields": { "title": "Auto Check", "description": "App para obtener informaci\u00f3n del veh\u00edculo", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -27,7 +27,7 @@ "title": "Encuentrame.pe", "description": "B\u00fasqueda de personas por medio de reconocimiento facial", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": false } @@ -37,8 +37,8 @@ "fields": { "title": "Social Faith", "description": "Sistema para promocionar campa\u00f1as de donaciones para ONG", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -48,8 +48,8 @@ "fields": { "title": "Entiende.pe", "description": "Plataforma para aprender de forma inteligente usando inteligencia artificial", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -60,7 +60,7 @@ "title": "Ayudame.pe", "description": "Plataforma que sirve como agente de cambio para mejorar la calidad de vida de personas", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": true } @@ -70,8 +70,8 @@ "fields": { "title": "Maiden Assistant", "description": "App que ofrece recomendaciones basados en la experiencia de la persona", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -81,8 +81,8 @@ "fields": { "title": "Estudia Por Gusto", "description": "App que ayuda a intercambiar conocimiento de forma interactiva", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": true } @@ -93,7 +93,7 @@ "title": "Blood care", "description": "Plataforma para encontrar una red de donantes de sangre voluntarios", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": false } @@ -103,8 +103,8 @@ "fields": { "title": "Trappist", "description": "Plataforma que nos ayuda a optimizar el tiempo de espera en los bancos usando sensores", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": true } @@ -114,8 +114,8 @@ "fields": { "title": "Ayllu FX", "description": "App para conectar y notificar personas para intercambiar divisas", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -126,7 +126,7 @@ "title": "I do it", "description": "Plataforma para conectar personas que requieren servicios de utilidad", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": true } @@ -136,8 +136,8 @@ "fields": { "title": "Aprende, Responde, Gana", "description": "", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -147,8 +147,8 @@ "fields": { "title": "YoTeAyudo", "description": "App para voluntarios con el objetivo de ayudar y colaborar con los dem\u00e1s", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": true } @@ -159,7 +159,7 @@ "title": "Parqueate", "description": "App para buscar zonas de estacionamiento", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": false } @@ -169,8 +169,8 @@ "fields": { "title": "ReciclaPe", "description": "App m\u00f3vil para mejorar el proceso de reciclaje", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -180,8 +180,8 @@ "fields": { "title": "EcoFriends", "description": "App de retos ecol\u00f3gicos para minimizar el impacto con la naturaleza", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": true } @@ -192,7 +192,7 @@ "title": "Co-Aprendamos.pe", "description": "Retos para aprender", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": true } @@ -202,8 +202,8 @@ "fields": { "title": "Smart Cacao", "description": "IOT aplicado a la agricultura, automatizar parcelas agricolas", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -213,8 +213,8 @@ "fields": { "title": "You Study", "description": "Comunidad de aprendizaje a trav\u00e9s de grupos de estudio", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -225,7 +225,7 @@ "title": "Regala una Sonrisa", "description": "Plataforma que gestiona donaciones de los usuarios para distribuir a ONG", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": true } @@ -235,8 +235,8 @@ "fields": { "title": "YoTambien", "description": "Sistema de capacitaci\u00f3n para personas con discapacidades", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -246,8 +246,8 @@ "fields": { "title": "Hogar SOS", "description": "Marketplace de servicios para el hogar, como carpinter\u00eda, gasfiter\u00eda, entre otros", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -258,7 +258,7 @@ "title": "iChancon", "description": "Red social para colaborar en la resoluci\u00f3n de problemas acad\u00e9micos", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": false } @@ -268,8 +268,8 @@ "fields": { "title": "iMetrics", "description": "Medidor de agua inteligente conectado a una red neural para optimizar facturaciones", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": true } @@ -279,8 +279,8 @@ "fields": { "title": "Mi Primer App", "description": "Participaci\u00f3n en proyectos para estudiantes de postgrado", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": true } @@ -291,7 +291,7 @@ "title": "B\u00fasqueda de Personas Perdidas", "description": "Plataforma para buscar personas por medio de reconocimiento facial", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": true } @@ -301,8 +301,8 @@ "fields": { "title": "Amor a Sofia", "description": "App para ayuda a los postulantes a universidades", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": true } @@ -312,8 +312,8 @@ "fields": { "title": "Medical Chain", "description": "Historiales m\u00e9dicos registrados en una red blockchain", - "author": 2, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -324,7 +324,7 @@ "title": "Gento", "description": "Plataforma que cambia la forma de como compramos ropa por medio de machine learning", "author": 4, - "event": 19, + "event": 2, "is_completed": false, "is_valid": false } @@ -334,8 +334,8 @@ "fields": { "title": "Sportify", "description": "App social para buscar gente que quiera jugar y este cerca de ti", - "author": 1, - "event": 19, + "author": 4, + "event": 2, "is_completed": false, "is_valid": false } @@ -345,9 +345,141 @@ "fields": { "title": "Dale Go", "description": "App social de eventos deportivos", - "author": 2, + "author": 4, + "event": 2, + "is_completed": false, + "is_valid": false + } +}, { + "model": "ideas.idea", + "pk": 33, + "fields": { + "title": "New Idea - Test", + "description": "Test description", + "author": 14, "event": 19, "is_completed": false, "is_valid": false } +}, { + "model": "ideas.idea", + "pk": 34, + "fields": { + "title": "new idea", + "description": "Test descrip", + "author": 14, + "event": 19, + "is_completed": false, + "is_valid": true + } +}, { + "model": "ideas.idea", + "pk": 35, + "fields": { + "title": "Test title", + "description": "Loren Ipsum is simply dummy text of the printing and typesetting industry. Loren Ipsum has been the industry\u2019s standar dummy....", + "author": 14, + "event": 19, + "is_completed": false, + "is_valid": false + } +}, { + "model": "ideas.idea", + "pk": 36, + "fields": { + "title": "new iOT idea", + "description": "Lorem Ipsum", + "author": 14, + "event": 19, + "is_completed": false, + "is_valid": false + } +}, { + "model": "ideas.idea", + "pk": 37, + "fields": { + "title": "New idea", + "description": "Test", + "author": 31, + "event": 19, + "is_completed": false, + "is_valid": false + } +}, { + "model": "ideas.idea", + "pk": 38, + "fields": { + "title": "J\u00fantate", + "description": "Las juntas son un medio mediante el cual muchos peruanos han logrado reunir sumas importantes de dinero sin necesidad de acceder a un cr\u00e9dito financiero. Creo que debemos automatizarla para poder realizar juntas con todo tipo de personas a pesar de las distancias f\u00edsicas.", + "author": 34, + "event": 19, + "is_completed": false, + "is_valid": true + } +}, { + "model": "ideas.idea", + "pk": 39, + "fields": { + "title": "An\u00e1lisis Financiero Personal con IA", + "description": "La idea se centra en el uso de la IA para analizar las deudas y el comportamiento de pago de una persona en el sistema financiero, y con datos que ingrese el usuario (como ingresos y gastos), se podr\u00e1 concluir con consejos para el usuario a fin de reducir su riesgo de impago o mejorara el perfil crediticio.", + "author": 33, + "event": 19, + "is_completed": false, + "is_valid": true + } +}, { + "model": "ideas.idea", + "pk": 40, + "fields": { + "title": "notificaciones TC", + "description": "Notificaciones al usar tu tarjeta de cr\u00e9dito o d\u00e9bito", + "author": 32, + "event": 19, + "is_completed": false, + "is_valid": true + } +}, { + "model": "ideas.idea", + "pk": 41, + "fields": { + "title": "Inversi\u00f3n en L\u00ednea - Fondos de Factoring / Sectores", + "description": "El objetivo es que las personas naturales puedan invertir desde sus hogares en fondos de factoring o confirming en empresas corporativas, grandes o mediana empresa. El sentido es tener una alternativa de inversi\u00f3n en fondos de inversi\u00f3n privados como p\u00fablicos.", + "author": 35, + "event": 19, + "is_completed": false, + "is_valid": true + } +}, { + "model": "ideas.idea", + "pk": 42, + "fields": { + "title": "Yotepresto.pe", + "description": "Una plataforma digital que permite interactuar a personas que necesitan financiaci\u00f3n con inversores que quieran rentabilizar sus ahorros prestando su dinero.", + "author": 36, + "event": 19, + "is_completed": true, + "is_valid": true + } +}, { + "model": "ideas.idea", + "pk": 43, + "fields": { + "title": "Paga.Pe", + "description": "Una plataforma en donde los prestamos entre personas naturales es registrada, pudiendo generar una cobranza automatizada, creando un historial crediticio de microprestamos para no bancarizados", + "author": 37, + "event": 19, + "is_completed": true, + "is_valid": true + } +}, { + "model": "ideas.idea", + "pk": 44, + "fields": { + "title": "Minqa", + "description": "Aplicaci\u00f3n que permitir\u00e1 conectar a organizaciones peruanas con aquellas personas interesadas en apoyarlas mediante donaciones monetarias", + "author": 38, + "event": 19, + "is_completed": true, + "is_valid": true + } }] \ No newline at end of file diff --git a/sample_data/role.json b/sample_data/role.json index 9a4502a..fd97850 100644 --- a/sample_data/role.json +++ b/sample_data/role.json @@ -34,4 +34,16 @@ "fields": { "name": "Fullstack Developer" } +}, { + "model": "participants.role", + "pk": 7, + "fields": { + "name": "Project Leader" + } +}, { + "model": "participants.role", + "pk": 8, + "fields": { + "name": "Other" + } }] \ No newline at end of file diff --git a/sample_data/users.json b/sample_data/users.json index 967d843..61e9b28 100644 --- a/sample_data/users.json +++ b/sample_data/users.json @@ -3,7 +3,7 @@ "pk": 1, "fields": { "password": "pbkdf2_sha256$30000$wck7Fjg9YlVJ$rMoCsMakvkI1oRmAlWCpvtNMmT6MVyOcrErcnaZWLOM=", - "last_login": "2018-05-11T01:10:41Z", + "last_login": "2018-05-17T16:43:57.274Z", "is_superuser": true, "email": "sinfante@belatrixsf.com", "first_name": "Sergio", @@ -128,7 +128,7 @@ "pk": 6, "fields": { "password": "pbkdf2_sha256$30000$OsIB0lLk0Zp7$5fI5vEE260SSRjrdlbRXbmEAQk0n6BOyk4gQzG/93Ks=", - "last_login": "2018-04-24T15:06:30Z", + "last_login": "2018-05-14T16:50:28.794Z", "is_superuser": true, "email": "developer@email.com", "first_name": "developer", @@ -228,17 +228,17 @@ "pk": 10, "fields": { "password": "pbkdf2_sha256$30000$k5Wh7imf3GJq$0fYP9VVDRCGrfFJb+1v9huWI6C8Ywb8LEbyH/wjHQk8=", - "last_login": null, + "last_login": "2018-05-15T19:14:27.044Z", "is_superuser": false, "email": "dvelasquez@belatrixsf.com", "first_name": "", "last_name": "", "full_name": "Diego Velasquez", "date_joined": "2018-05-08T18:24:29.061Z", - "phone_number": "", + "phone_number": "3434", "role": 3, "is_staff": true, - "is_jury": false, + "is_jury": true, "is_moderator": true, "is_active": true, "is_blocked": false, @@ -273,4 +273,304 @@ "groups": [], "user_permissions": [] } +}, { + "model": "participants.user", + "pk": 12, + "fields": { + "password": "pbkdf2_sha256$30000$gxuiF7ATLltt$GM14HL611hFmQ994zUYTQBY23IoNowWtrvA4A/ZBUC4=", + "last_login": null, + "is_superuser": false, + "email": "lburgos@belatrixsf.com", + "first_name": "", + "last_name": "", + "full_name": "Luis Miguel Burgos", + "date_joined": "2018-05-14T17:05:46.074Z", + "phone_number": "987654321", + "role": 3, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": false, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 13, + "fields": { + "password": "pbkdf2_sha256$30000$MvHHJWbESjfr$SC0To/fVl44Fw8pOEbRf5zBZkdwPm66niXNYY7roXCw=", + "last_login": null, + "is_superuser": false, + "email": "cmonzon@gmail.com", + "first_name": "", + "last_name": "", + "full_name": null, + "date_joined": "2018-05-15T03:48:46.539Z", + "phone_number": null, + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": null, + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 14, + "fields": { + "password": "pbkdf2_sha256$30000$k3mz0AtDe3d3$Aqut51YJkm0/GWG7lXDKQ4rm2RCtnuu0U4LnfqkWjSo=", + "last_login": null, + "is_superuser": false, + "email": "carlosmonzonsalvador@gmail.com", + "first_name": "", + "last_name": "", + "full_name": null, + "date_joined": "2018-05-15T03:49:24.424Z", + "phone_number": null, + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": null, + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 15, + "fields": { + "password": "pbkdf2_sha256$30000$es6h3PLmuHhr$yJdsASx0pEaOYa5zSODs/oTR2Rjk+a/332Pseii0tlw=", + "last_login": null, + "is_superuser": false, + "email": "diegoveloper@gmail.com", + "first_name": "", + "last_name": "", + "full_name": "Diego Vel", + "date_joined": "2018-05-15T04:02:17.846Z", + "phone_number": "993296869", + "role": 6, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": null, + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 31, + "fields": { + "password": "pbkdf2_sha256$30000$GwM6eksaLWYP$G0F8k2rEPq5lLyfMX4fbtTG6hk/8Xpydl0n+TRWXYQ8=", + "last_login": null, + "is_superuser": false, + "email": "h2119184@nwytg.com", + "first_name": "", + "last_name": "", + "full_name": "Test Account", + "date_joined": "2018-05-17T04:37:13.256Z", + "phone_number": "99999999", + "role": 1, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": null, + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 32, + "fields": { + "password": "pbkdf2_sha256$30000$2XBXiQz3NfKa$PhWaz/OeH5dO5hPGauriU3+aVxC5hRvgHjuJWDEo3j8=", + "last_login": null, + "is_superuser": false, + "email": "mobilidad.skysoft@gmail.com", + "first_name": "", + "last_name": "", + "full_name": "Alonso Arana", + "date_joined": "2018-05-17T16:49:39.827Z", + "phone_number": "", + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 33, + "fields": { + "password": "pbkdf2_sha256$30000$oayn07BiIjk1$xcx+NkMFFuk8qkNhmlP8tkFBVok5LTGBeCSZ1/tgv1w=", + "last_login": null, + "is_superuser": false, + "email": "jemilcastroc@gmail.com", + "first_name": "", + "last_name": "", + "full_name": "Jemil Castro", + "date_joined": "2018-05-17T16:51:44.231Z", + "phone_number": "", + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 34, + "fields": { + "password": "pbkdf2_sha256$30000$3olxfHn8aSKX$hF9KdMvYL5K/DW0mkBY3XZuMLgyA6G7jMne4PdpYDIg=", + "last_login": null, + "is_superuser": false, + "email": "scyrizales@gmail.com", + "first_name": "", + "last_name": "", + "full_name": "Sergio Castillo Yrizales", + "date_joined": "2018-05-17T16:59:05.906Z", + "phone_number": "", + "role": 6, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 35, + "fields": { + "password": "pbkdf2_sha256$30000$KgHVFodPSDgW$+Bh0PojV39sc3Ct6RJCe9eCg8ReqjlG7usvY1/O4rB8=", + "last_login": null, + "is_superuser": false, + "email": "j_aspas@hotmail.com", + "first_name": "", + "last_name": "", + "full_name": "Jimmy Eduard Astocondor Pastor", + "date_joined": "2018-05-17T17:15:03.866Z", + "phone_number": "", + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 36, + "fields": { + "password": "pbkdf2_sha256$30000$iLU8MUugcJZr$NKDQg6x8i2DMbLTLUZOEI1OsAUQYYOdZ+VOSrfcilZ8=", + "last_login": null, + "is_superuser": false, + "email": "fredyhuamansalcedo@gmail.com", + "first_name": "", + "last_name": "", + "full_name": "Fredy Huam\u00e1n", + "date_joined": "2018-05-17T17:16:50.479Z", + "phone_number": "", + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 37, + "fields": { + "password": "pbkdf2_sha256$30000$tbDwecmce3DG$DlTt5rQP9NG4ZyzujSEQ5Je9JtVIUjOxTvXovVCvYEA=", + "last_login": null, + "is_superuser": false, + "email": "alopez@starter.pe", + "first_name": "", + "last_name": "", + "full_name": "Anthony Lopez", + "date_joined": "2018-05-17T17:17:37.443Z", + "phone_number": "", + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } +}, { + "model": "participants.user", + "pk": 38, + "fields": { + "password": "pbkdf2_sha256$30000$J3w0Q1VJI0od$Bfirdazv3myBWpOvcnDiiV0UlC5wf9nRnLtFndEp6xE=", + "last_login": null, + "is_superuser": false, + "email": "qodeperu@gmail.com", + "first_name": "", + "last_name": "", + "full_name": "Rosario Cortijo", + "date_joined": "2018-05-17T17:18:15.151Z", + "phone_number": "", + "role": null, + "is_staff": false, + "is_jury": false, + "is_moderator": false, + "is_active": true, + "is_blocked": false, + "is_password_reset_required": true, + "reset_password_code": null, + "temporary_password": "", + "groups": [], + "user_permissions": [] + } }] \ No newline at end of file From f1f8a1d29cec1f8248c27947d553a2050eda3e48 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Thu, 17 May 2018 14:08:34 -0500 Subject: [PATCH 39/72] Add report for valid idea list --- reports/templates/idea_list.html | 26 ++++++++++++++++++++++++ reports/templates/index.html | 15 +++++++++++--- reports/templates/participants_idea.html | 6 ++---- reports/urls.py | 2 ++ reports/views.py | 12 +++++++++-- 5 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 reports/templates/idea_list.html diff --git a/reports/templates/idea_list.html b/reports/templates/idea_list.html new file mode 100644 index 0000000..2e32e1a --- /dev/null +++ b/reports/templates/idea_list.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% block content %} + + + + + + + + + + {% for register in idea_list %} + + + + + + + + + {% empty %} + + {% endfor %} +
#TitleAuthor emailDescriptionIs Completed?Is Valid?
{{ forloop.counter }}{{ register.title }}{{ register.author.email }}{{ register.description }}{{ register.is_completed }}{{ register.is_valid }}
no content.
+

Back

+{% endblock content %} \ No newline at end of file diff --git a/reports/templates/index.html b/reports/templates/index.html index 19944bd..3312e69 100644 --- a/reports/templates/index.html +++ b/reports/templates/index.html @@ -1,8 +1,17 @@ {% extends 'base.html' %} {% block content %}

Reports

-

-

-

+
+

Events

+

+

+

+
+
+

Participants

+

+

+

+
{% endblock content %} diff --git a/reports/templates/participants_idea.html b/reports/templates/participants_idea.html index 443819e..4d7e94d 100644 --- a/reports/templates/participants_idea.html +++ b/reports/templates/participants_idea.html @@ -4,16 +4,14 @@ # Idea Title - Participant Firstname - Participant Lastname + Participant Full Name Participant email {% for register in participants_list %} {{ forloop.counter }} {{ register.idea }} - {{ register.user.first_name }} - {{ register.user.last_name }} + {{ register.user.full_name }} {{ register.user.email }} {% empty %} diff --git a/reports/urls.py b/reports/urls.py index a9806da..c20df3c 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -1,9 +1,11 @@ from django.conf.urls import url from .views import index +from .views import idea_list from .views import participants_idea urlpatterns = [ url(r'^$', index, name='index'), url(r'^participants/idea/$', participants_idea, name='participants_idea'), + url(r'^event/(?P\d+)/idea/list/$', idea_list, name='idea_list'), ] diff --git a/reports/views.py b/reports/views.py index c939564..913f68b 100644 --- a/reports/views.py +++ b/reports/views.py @@ -1,6 +1,7 @@ -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 -from ideas.models import IdeaParticipant +from events.models import Event +from ideas.models import Idea, IdeaParticipant def index(request): @@ -11,3 +12,10 @@ def participants_idea(request): participants_list = IdeaParticipant.objects.all() context = {'participants_list': participants_list} return render(request, 'participants_idea.html', context) + + +def idea_list(request, event_id): + event = get_object_or_404(Event, pk=event_id) + list = Idea.objects.filter(event=event, is_valid=True) + context = {'idea_list': list} + return render(request, 'idea_list.html', context) From 2092ac3299f01c1bc8adbbfe977cd8c5712b7807 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Thu, 17 May 2018 22:30:28 -0500 Subject: [PATCH 40/72] Add endpoint to activate/deactivate user --- participants/urls.py | 3 ++- participants/views.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/participants/urls.py b/participants/urls.py index 9071907..83a98e3 100644 --- a/participants/urls.py +++ b/participants/urls.py @@ -1,5 +1,5 @@ from django.conf.urls import url -from .views import user_profile, user_detail, user_list +from .views import user_profile, user_detail, user_list, user_activation from .views import user_creation, user_update, user_update_password, CustomAuthToken from .views import user_password_recovery_request, user_password_recovery_confirmation, user_logout, user_roles from ideas.views import my_ideas @@ -7,6 +7,7 @@ urlpatterns = [ url(r'^(?P\d+)/$', user_detail, name='user_detail'), + url(r'^(?P\d+)/activation/switch/$', user_activation, name='user_activation'), url(r'^authenticate/', CustomAuthToken.as_view()), url(r'^create/$', user_creation, name='user_creation'), url(r'^list/$', user_list, name='user_list'), diff --git a/participants/views.py b/participants/views.py index d7eacca..66d524e 100644 --- a/participants/views.py +++ b/participants/views.py @@ -15,7 +15,7 @@ from rest_framework.response import Response from .models import User, Participant, Role -from .permissions import IsModerator +from .permissions import IsModerator, IsStaff from .serializers import UserSerializer, UserCreationSerializer from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer, RoleSerializer from .serializers import EventProfileSerializer, IdeaProfileSerializer, AttendanceProfileSerializer @@ -314,3 +314,22 @@ def user_logout(request): """ logout(request) return Response(status=status.HTTP_202_ACCEPTED) + + +@api_view(['PATCH', ]) +@permission_classes((IsStaff, )) +def user_activation(request, user_id): + """ + Deactivate user + --- + PATCH: + response_serializer: participants.serializers.UserSerializer + """ + user = get_object_or_404(User, pk=user_id) + if user.is_active: + user.is_active = False + else: + user.is_active = True + user.save() + serializer = UserSerializer(user) + return Response(serializer.data, status=status.HTTP_202_ACCEPTED) From d22a17bcd6087ca4947347b5fbb3f677cb41ca74 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 09:36:47 -0500 Subject: [PATCH 41/72] Change to optional fields update user serializer and view. --- participants/serializers.py | 6 +++--- participants/views.py | 17 ++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/participants/serializers.py b/participants/serializers.py index 4bfaf4e..e3b88a3 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -33,9 +33,9 @@ class UserCreationSerializer(serializers.Serializer): class UserProfileSerializer(serializers.Serializer): - full_name = serializers.CharField(max_length=255) - phone_number = serializers.IntegerField() - role_id = serializers.IntegerField() + full_name = serializers.CharField(max_length=255, required=False) + phone_number = serializers.IntegerField(required=False) + role_id = serializers.IntegerField(required=False) class UserAuthenticationSerializer(serializers.Serializer): diff --git a/participants/views.py b/participants/views.py index 66d524e..98f18b8 100644 --- a/participants/views.py +++ b/participants/views.py @@ -162,14 +162,17 @@ def user_update(request): serializer = UserProfileSerializer(data=request.data) if serializer.is_valid(raise_exception=True): - full_name = serializer.validated_data['full_name'] - phone_number = serializer.validated_data['phone_number'] - role_id = serializer.validated_data['role_id'] - role = Role.objects.get(pk=role_id) current_user = request.user - current_user.full_name = full_name - current_user.phone_number = phone_number - current_user.role = role + if 'full_name' in serializer.validated_data: + full_name = serializer.validated_data['full_name'] + current_user.full_name = full_name + if 'phone_number' in serializer.validated_data: + phone_number = serializer.validated_data['phone_number'] + current_user.phone_number = phone_number + if 'role_id' in serializer.validated_data: + role_id = serializer.validated_data['role_id'] + role = Role.objects.get(pk=role_id) + current_user.role = role current_user.save() serializer = UserSerializer(current_user) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) From 988b978255d19c8af3e751d1962f6015fc6744de Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 11:27:40 -0500 Subject: [PATCH 42/72] Add meetings attendance reports --- reports/templates/attendance_list.html | 24 ++++++++++++++++++++++ reports/templates/index.html | 1 + reports/templates/meeting_list.html | 28 ++++++++++++++++++++++++++ reports/urls.py | 4 ++++ reports/views.py | 14 ++++++++++++- 5 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 reports/templates/attendance_list.html create mode 100644 reports/templates/meeting_list.html diff --git a/reports/templates/attendance_list.html b/reports/templates/attendance_list.html new file mode 100644 index 0000000..c9c1e00 --- /dev/null +++ b/reports/templates/attendance_list.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block content %} + + + + + + + + + {% for register in attendance_list %} + + + + + + + + {% empty %} + + {% endfor %} +
#Meeting NameParticipant fullnameParticipant emailRegister datetime
{{ forloop.counter }}{{ register.meeting.name }}{{ register.participant.full_name }}{{ register.participant.email }}{{ register.datetime }}
no content.
+

Back

+{% endblock content %} \ No newline at end of file diff --git a/reports/templates/index.html b/reports/templates/index.html index 3312e69..e7931ab 100644 --- a/reports/templates/index.html +++ b/reports/templates/index.html @@ -5,6 +5,7 @@

Reports

Events

+

diff --git a/reports/templates/meeting_list.html b/reports/templates/meeting_list.html new file mode 100644 index 0000000..8d3d191 --- /dev/null +++ b/reports/templates/meeting_list.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} +{% block content %} + + + + + + + + + + + {% for register in meeting_list %} + + + + + + + + + + {% empty %} + + {% endfor %} +
#NameEventStart dateEnd dateIs Over?Is Active?
{{ forloop.counter }}{{ register.name }}{{ register.event.title }}{{ register.start_date }}{{ register.end_date }}{{ register.is_over }}{{ register.is_active }}
no content.
+

Back

+{% endblock content %} \ No newline at end of file diff --git a/reports/urls.py b/reports/urls.py index c20df3c..9dd228d 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -2,10 +2,14 @@ from .views import index from .views import idea_list +from .views import meeting_attendance_detail, meeting_list from .views import participants_idea + urlpatterns = [ url(r'^$', index, name='index'), url(r'^participants/idea/$', participants_idea, name='participants_idea'), url(r'^event/(?P\d+)/idea/list/$', idea_list, name='idea_list'), + url(r'^meeting/list/$', meeting_list, name='meeting_list'), + url(r'^meeting/(?P\d+)/attendance/list/$', meeting_attendance_detail, name='attendance_list'), ] diff --git a/reports/views.py b/reports/views.py index 913f68b..7ff5dc2 100644 --- a/reports/views.py +++ b/reports/views.py @@ -1,6 +1,6 @@ from django.shortcuts import render, get_object_or_404 -from events.models import Event +from events.models import Attendance, Event, Meeting from ideas.models import Idea, IdeaParticipant @@ -19,3 +19,15 @@ def idea_list(request, event_id): list = Idea.objects.filter(event=event, is_valid=True) context = {'idea_list': list} return render(request, 'idea_list.html', context) + + +def meeting_list(request): + meetings = Meeting.objects.all() + context = {'meeting_list': meetings} + return render(request, 'meeting_list.html', context) + + +def meeting_attendance_detail(request, meeting_id): + attendance_list = Attendance.objects.filter(meeting=meeting_id) + context = {'attendance_list': attendance_list} + return render(request, 'attendance_list.html', context) From 9a0ed52da281db038a3e36968fb8d62d15d4f9ef Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 11:42:08 -0500 Subject: [PATCH 43/72] Modify update user view in order to allow empty values --- participants/views.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/participants/views.py b/participants/views.py index 98f18b8..42c91ef 100644 --- a/participants/views.py +++ b/participants/views.py @@ -163,16 +163,23 @@ def user_update(request): if serializer.is_valid(raise_exception=True): current_user = request.user + if 'full_name' in serializer.validated_data: - full_name = serializer.validated_data['full_name'] - current_user.full_name = full_name + current_user.full_name = serializer.validated_data['full_name'] + else: + current_user.full_name = None + if 'phone_number' in serializer.validated_data: - phone_number = serializer.validated_data['phone_number'] - current_user.phone_number = phone_number + current_user.phone_number = serializer.validated_data['phone_number'] + else: + current_user.phone_number = None + if 'role_id' in serializer.validated_data: role_id = serializer.validated_data['role_id'] - role = Role.objects.get(pk=role_id) - current_user.role = role + current_user.role = Role.objects.get(pk=role_id) + else: + current_user.role = None + current_user.save() serializer = UserSerializer(current_user) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) From 5d820413a376c080291d13dca46e35e02e024606 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 12:30:41 -0500 Subject: [PATCH 44/72] Add report for ideas in development --- reports/templates/index.html | 7 ++++++- reports/urls.py | 3 ++- reports/views.py | 6 ++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/reports/templates/index.html b/reports/templates/index.html index e7931ab..5060977 100644 --- a/reports/templates/index.html +++ b/reports/templates/index.html @@ -8,6 +8,12 @@

Events

+
+

Ideas

+

+

+

+

Participants

@@ -15,4 +21,3 @@

Participants

{% endblock content %} - diff --git a/reports/urls.py b/reports/urls.py index 9dd228d..9aa201b 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from .views import index -from .views import idea_list +from .views import idea_list, idea_in_development from .views import meeting_attendance_detail, meeting_list from .views import participants_idea @@ -10,6 +10,7 @@ url(r'^$', index, name='index'), url(r'^participants/idea/$', participants_idea, name='participants_idea'), url(r'^event/(?P\d+)/idea/list/$', idea_list, name='idea_list'), + url(r'^idea/development/list/$', idea_in_development, name='ideas_in_development'), url(r'^meeting/list/$', meeting_list, name='meeting_list'), url(r'^meeting/(?P\d+)/attendance/list/$', meeting_attendance_detail, name='attendance_list'), ] diff --git a/reports/views.py b/reports/views.py index 7ff5dc2..0e3ebd5 100644 --- a/reports/views.py +++ b/reports/views.py @@ -21,6 +21,12 @@ def idea_list(request, event_id): return render(request, 'idea_list.html', context) +def idea_in_development(request): + list = Idea.objects.filter(is_completed=True) + context = {'idea_list': list} + return render(request, 'idea_list.html', context) + + def meeting_list(request): meetings = Meeting.objects.all() context = {'meeting_list': meetings} From e8b4a7494e67e8ebdde1f7a62d9900e8910539af Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 13:41:26 -0500 Subject: [PATCH 45/72] Add ideas votes results report --- reports/templates/idea_vote_results.html | 20 ++++++++++++++++++++ reports/templates/index.html | 3 ++- reports/urls.py | 3 ++- reports/views.py | 10 +++++++++- 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 reports/templates/idea_vote_results.html diff --git a/reports/templates/idea_vote_results.html b/reports/templates/idea_vote_results.html new file mode 100644 index 0000000..f53c096 --- /dev/null +++ b/reports/templates/idea_vote_results.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% block content %} + + + + + + + {% for register in results %} + + + + + + {% empty %} + + {% endfor %} +
#Idea TitleVotes
{{ forloop.counter }}{{ register.idea__title }}{{ register.total }}
no content.
+

Back

+{% endblock content %} \ No newline at end of file diff --git a/reports/templates/index.html b/reports/templates/index.html index 5060977..86fcda7 100644 --- a/reports/templates/index.html +++ b/reports/templates/index.html @@ -11,7 +11,8 @@

Events

diff --git a/reports/urls.py b/reports/urls.py index 9aa201b..932a026 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from .views import index -from .views import idea_list, idea_in_development +from .views import idea_list, idea_in_development, idea_vote_results from .views import meeting_attendance_detail, meeting_list from .views import participants_idea @@ -11,6 +11,7 @@ url(r'^participants/idea/$', participants_idea, name='participants_idea'), url(r'^event/(?P\d+)/idea/list/$', idea_list, name='idea_list'), url(r'^idea/development/list/$', idea_in_development, name='ideas_in_development'), + url(r'^idea/vote/results/$', idea_vote_results, name='ideas_vote_results'), url(r'^meeting/list/$', meeting_list, name='meeting_list'), url(r'^meeting/(?P\d+)/attendance/list/$', meeting_attendance_detail, name='attendance_list'), ] diff --git a/reports/views.py b/reports/views.py index 0e3ebd5..0b9c1b1 100644 --- a/reports/views.py +++ b/reports/views.py @@ -1,7 +1,8 @@ +from django.db.models import Count from django.shortcuts import render, get_object_or_404 from events.models import Attendance, Event, Meeting -from ideas.models import Idea, IdeaParticipant +from ideas.models import Idea, IdeaParticipant, IdeaVotes def index(request): @@ -27,6 +28,13 @@ def idea_in_development(request): return render(request, 'idea_list.html', context) +def idea_vote_results(request): + results = IdeaVotes.objects.all().values('idea__title', + 'idea__id').annotate(total=Count('participant')).order_by('-total') + context = {'results': results} + return render(request, 'idea_vote_results.html', context) + + def meeting_list(request): meetings = Meeting.objects.all() context = {'meeting_list': meetings} From c4e399ca1e95f81dd73a292548006d07c1fa607a Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 15:59:01 -0500 Subject: [PATCH 46/72] Fix idea draft list url in order to support / at the end --- ideas/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ideas/urls.py b/ideas/urls.py index 7e723c1..7b933a2 100644 --- a/ideas/urls.py +++ b/ideas/urls.py @@ -22,5 +22,5 @@ # url(r'^(?P\d+)/vote/$', idea_vote, name='idea_vote'), url(r'^create/$', idea_create, name='idea_create'), url(r'^rate/category/list/$', idea_scores_criteria, name='idea_scores_criteria'), - url(r'^draft/event/(?P\d+)/list$', idea_draft_list, name='idea_draft_list'), + url(r'^draft/event/(?P\d+)/list/$', idea_draft_list, name='idea_draft_list'), ] From ffe53ad666679712509608214f92f247007ee2c9 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 18:55:15 -0500 Subject: [PATCH 47/72] Add idea jury values report --- reports/templates/index.html | 1 + reports/urls.py | 3 ++- reports/views.py | 8 +++++++- sample_data/score_criteria.json | 17 +++++------------ 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/reports/templates/index.html b/reports/templates/index.html index 86fcda7..a578ade 100644 --- a/reports/templates/index.html +++ b/reports/templates/index.html @@ -13,6 +13,7 @@

Ideas

+

diff --git a/reports/urls.py b/reports/urls.py index 932a026..50f8cf9 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -1,7 +1,7 @@ from django.conf.urls import url from .views import index -from .views import idea_list, idea_in_development, idea_vote_results +from .views import idea_list, idea_in_development, idea_vote_results, idea_jury_results from .views import meeting_attendance_detail, meeting_list from .views import participants_idea @@ -12,6 +12,7 @@ url(r'^event/(?P\d+)/idea/list/$', idea_list, name='idea_list'), url(r'^idea/development/list/$', idea_in_development, name='ideas_in_development'), url(r'^idea/vote/results/$', idea_vote_results, name='ideas_vote_results'), + url(r'^idea/jury/results/$', idea_jury_results, name='idea_jury_results'), url(r'^meeting/list/$', meeting_list, name='meeting_list'), url(r'^meeting/(?P\d+)/attendance/list/$', meeting_attendance_detail, name='attendance_list'), ] diff --git a/reports/views.py b/reports/views.py index 0b9c1b1..d97e4fa 100644 --- a/reports/views.py +++ b/reports/views.py @@ -2,7 +2,7 @@ from django.shortcuts import render, get_object_or_404 from events.models import Attendance, Event, Meeting -from ideas.models import Idea, IdeaParticipant, IdeaVotes +from ideas.models import Idea, IdeaParticipant, IdeaVotes, IdeaScores def index(request): @@ -35,6 +35,12 @@ def idea_vote_results(request): return render(request, 'idea_vote_results.html', context) +def idea_jury_results(request): + results = IdeaScores.objects.all().order_by('idea') + context = {'results': results} + return render(request, 'idea_jury_results.html', context) + + def meeting_list(request): meetings = Meeting.objects.all() context = {'meeting_list': meetings} diff --git a/sample_data/score_criteria.json b/sample_data/score_criteria.json index 68d327d..2740e38 100644 --- a/sample_data/score_criteria.json +++ b/sample_data/score_criteria.json @@ -3,27 +3,20 @@ "pk": 1, "fields": { "name": "Innovaci\u00f3n", - "weight": 4 - } -}, { - "model": "ideas.ideascorescriteria", - "pk": 2, - "fields": { - "name": "Funcionalidad", - "weight": 3 + "weight": 40 } }, { "model": "ideas.ideascorescriteria", "pk": 3, "fields": { - "name": "Presentaci\u00f3n", - "weight": 1 + "name": "Usabilidad", + "weight": 30 } }, { "model": "ideas.ideascorescriteria", "pk": 4, "fields": { - "name": "Complejidad", - "weight": 2 + "name": "Factibilidad", + "weight": 30 } }] \ No newline at end of file From cbc5c5f99e878caa50ba1a56eb371bc24d196aa9 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 18 May 2018 18:57:56 -0500 Subject: [PATCH 48/72] Add idea jury values report template --- reports/templates/idea_jury_results.html | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 reports/templates/idea_jury_results.html diff --git a/reports/templates/idea_jury_results.html b/reports/templates/idea_jury_results.html new file mode 100644 index 0000000..3c753a6 --- /dev/null +++ b/reports/templates/idea_jury_results.html @@ -0,0 +1,26 @@ +{% extends 'base.html' %} +{% block content %} + + + + + + + + + + {% for register in results %} + + + + + + + + + {% empty %} + + {% endfor %} +
#TitleAuthor emailDescriptionIs Completed?Is Valid?
{{ forloop.counter }}{{ register.idea.title }}{{ register.jury.full_name }}{{ register.category }}{{ register.category.weight }}{{ register.value }}
no content.
+

Back

+{% endblock content %} \ No newline at end of file From 9f728cb8ce70321c654505bb4f974afed2a82f48 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 19 May 2018 12:28:19 -0500 Subject: [PATCH 49/72] Add event detail report --- reports/templates/event.html | 30 ++++++++++++++++++++++++ reports/templates/idea_jury_results.html | 10 ++++---- reports/templates/index.html | 1 + reports/urls.py | 3 ++- reports/views.py | 21 ++++++++++++++++- 5 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 reports/templates/event.html diff --git a/reports/templates/event.html b/reports/templates/event.html new file mode 100644 index 0000000..144c41b --- /dev/null +++ b/reports/templates/event.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} +{% block content %} + + + + + + + + + + +
Event
Title{{ event.title }}
Datetime{{ event.datetime }}
Address{{ event.address }}
Participants Registered{{ participants_registered | length }}
Proposed ideas{{ ideas_proposed | length }}
Valid Ideas {{ ideas_valid | length }}
Team members{{ team_members | length }}
Votes{{ votes | length }}
+ + + + + {% for register in attendances %} + + + + + + {% empty %} + + {% endfor %} +
Meetings Attendance
#MeetingAssistants
{{ forloop.counter }}{{ register.meeting__name }}{{ register.total }}
no content.
+ +

Back

+{% endblock content %} \ No newline at end of file diff --git a/reports/templates/idea_jury_results.html b/reports/templates/idea_jury_results.html index 3c753a6..51f0d1a 100644 --- a/reports/templates/idea_jury_results.html +++ b/reports/templates/idea_jury_results.html @@ -3,11 +3,11 @@ - - - - - + + + + + {% for register in results %} diff --git a/reports/templates/index.html b/reports/templates/index.html index a578ade..dde82ce 100644 --- a/reports/templates/index.html +++ b/reports/templates/index.html @@ -4,6 +4,7 @@

Reports

Events

+

diff --git a/reports/urls.py b/reports/urls.py index 50f8cf9..943008a 100644 --- a/reports/urls.py +++ b/reports/urls.py @@ -1,6 +1,6 @@ from django.conf.urls import url -from .views import index +from .views import index, event_information from .views import idea_list, idea_in_development, idea_vote_results, idea_jury_results from .views import meeting_attendance_detail, meeting_list from .views import participants_idea @@ -9,6 +9,7 @@ urlpatterns = [ url(r'^$', index, name='index'), url(r'^participants/idea/$', participants_idea, name='participants_idea'), + url(r'^event/(?P\d+)/$', event_information, name='event_information'), url(r'^event/(?P\d+)/idea/list/$', idea_list, name='idea_list'), url(r'^idea/development/list/$', idea_in_development, name='ideas_in_development'), url(r'^idea/vote/results/$', idea_vote_results, name='ideas_vote_results'), diff --git a/reports/views.py b/reports/views.py index d97e4fa..b9421ec 100644 --- a/reports/views.py +++ b/reports/views.py @@ -1,7 +1,7 @@ from django.db.models import Count from django.shortcuts import render, get_object_or_404 -from events.models import Attendance, Event, Meeting +from events.models import Attendance, Event, Meeting, EventParticipant from ideas.models import Idea, IdeaParticipant, IdeaVotes, IdeaScores @@ -9,6 +9,25 @@ def index(request): return render(request, 'index.html') +def event_information(request, event_id): + event = get_object_or_404(Event, pk=event_id) + participants_registered = EventParticipant.objects.filter(event=event) + attendances = Attendance.objects.filter(meeting__event=event).values('meeting__name').annotate( + total=Count('participant')).order_by('total') + ideas_proposed = Idea.objects.filter(event=event) + ideas_valid = Idea.objects.filter(event=event, is_valid=True) + team_members = IdeaParticipant.objects.filter(idea__event=event) + votes = IdeaVotes.objects.filter(event=event) + context = {'event': event, + 'participants_registered': participants_registered, + 'attendances': attendances, + 'ideas_proposed': ideas_proposed, + 'ideas_valid': ideas_valid, + 'team_members': team_members, + 'votes': votes} + return render(request, 'event.html', context) + + def participants_idea(request): participants_list = IdeaParticipant.objects.all() context = {'participants_list': participants_list} From 5cf463686aa594786d4647e3bec5e711422638f8 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 19 May 2018 21:34:45 -0500 Subject: [PATCH 50/72] Change IntegerField to CharField to allow phone number string format --- participants/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/participants/serializers.py b/participants/serializers.py index e3b88a3..30cc991 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -34,7 +34,7 @@ class UserCreationSerializer(serializers.Serializer): class UserProfileSerializer(serializers.Serializer): full_name = serializers.CharField(max_length=255, required=False) - phone_number = serializers.IntegerField(required=False) + phone_number = serializers.CharField(required=False) role_id = serializers.IntegerField(required=False) From 66528282ce6185288632db4d9f7145a4c4bcaf5e Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sun, 20 May 2018 10:19:09 -0500 Subject: [PATCH 51/72] Add optional id parameter to get user profile information --- participants/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/participants/views.py b/participants/views.py index 42c91ef..fd1d617 100644 --- a/participants/views.py +++ b/participants/views.py @@ -77,8 +77,17 @@ def user_profile(request): --- GET: response_serializer: participants.serializers.UserSerializer + parameters: + - name: id + description: user id + type: int + required: false + paramType: query """ - user = request.user + if request.GET.get('id'): + user = get_object_or_404(User, pk=request.GET.get('id')) + else: + user = request.user events = EventParticipant.objects.filter(participant=user) events_serializer = EventProfileSerializer(events, many=True) From 2afda552ab5d816bfa519bc20f64d7428e7b9823 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sun, 20 May 2018 12:56:04 -0500 Subject: [PATCH 52/72] Add validation to unregister participant in order to support it even if the user is not a candidate --- ideas/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ideas/views.py b/ideas/views.py index a4660a1..1c96bb8 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -270,9 +270,12 @@ def idea_unregister(request, idea_id): user = get_object_or_404(User, pk=serializer.validated_data['user_id']) get_object_or_404(IdeaParticipant, idea=idea, user=user).delete() - candidate = get_object_or_404(IdeaCandidate, idea=idea, user=user) - candidate.is_accepted = False - candidate.save() + try: + candidate = IdeaCandidate.objects.get(idea=idea, user=user) + candidate.is_accepted = False + candidate.save() + except: + pass if IdeaParticipant.objects.filter(idea=idea).count() < config.TEAM_MIN_SIZE: idea.is_completed = False From 33e2239dd5e05d147622bd72ec24abe96d7fdb3b Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sun, 20 May 2018 23:10:33 -0500 Subject: [PATCH 53/72] Create default jury scores values in order to improve assessment flow --- ideas/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ideas/views.py b/ideas/views.py index 1c96bb8..4566898 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -473,6 +473,12 @@ def idea_rate(request, idea_id): idea_score = get_object_or_404(IdeaScores, idea=idea, jury=user, category=category) idea_score.value = value idea_score.save() - idea_scores = IdeaScores.objects.filter(idea=idea, jury=user) + if request.method == "GET": + idea_scores = IdeaScores.objects.filter(idea=idea, jury=user) + if len(idea_scores) == 0: + categories = IdeaScoresCriteria.objects.all() + for category in categories: + IdeaScores.objects.create(idea=idea, jury=user, category=category, value=0) + idea_scores = IdeaScores.objects.filter(idea=idea, jury=user) serializer = IdeaScoreModelSerializer(idea_scores, many=True) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) From b1c12981217bda9329aab3317101ca8def2f6e51 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Mon, 21 May 2018 06:42:03 -0500 Subject: [PATCH 54/72] Fix error for PATCH method using jury rate endpoint about local variable referenced before assignment --- ideas/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ideas/views.py b/ideas/views.py index 4566898..411b1b9 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -479,6 +479,6 @@ def idea_rate(request, idea_id): categories = IdeaScoresCriteria.objects.all() for category in categories: IdeaScores.objects.create(idea=idea, jury=user, category=category, value=0) - idea_scores = IdeaScores.objects.filter(idea=idea, jury=user) + idea_scores = IdeaScores.objects.filter(idea=idea, jury=user) serializer = IdeaScoreModelSerializer(idea_scores, many=True) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) From eb93c789e689351d9871942cb0d81996fa23c17c Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Mon, 21 May 2018 19:12:46 -0500 Subject: [PATCH 55/72] Change sample data to test event list flow --- events/admin.py | 2 +- sample_data/events.json | 6 +++--- sample_data/idea_scores.json | 18 ------------------ 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/events/admin.py b/events/admin.py index d65d060..5036c2e 100644 --- a/events/admin.py +++ b/events/admin.py @@ -8,7 +8,7 @@ class CityAdmin(admin.ModelAdmin): class EventAdmin(admin.ModelAdmin): - list_display = ('title', 'datetime', 'image', 'details', 'is_upcoming', 'is_featured', 'get_cities') + list_display = ('title', 'datetime', 'image', 'details', 'is_upcoming', 'is_featured', 'get_cities', 'is_active') class EventParticipantAdmin(admin.ModelAdmin): diff --git a/sample_data/events.json b/sample_data/events.json index a0ea924..c704474 100644 --- a/sample_data/events.json +++ b/sample_data/events.json @@ -160,10 +160,10 @@ "has_interactions": false, "interaction_text": "", "interaction_confirmation_text": "", - "is_featured": true, + "is_featured": false, "is_upcoming": false, "is_interaction_active": false, - "is_active": false, + "is_active": true, "city": [3] } }, { @@ -172,7 +172,7 @@ "fields": { "title": "Hackatrix Lima 2018", "image": "http://i.imgur.com/bTHbsu5.jpg", - "datetime": "2018-06-02T14:00:00Z", + "datetime": "2018-05-26T14:00:00Z", "address": "Oficinas BCP", "details": "Es la Hackathon anual de Belatrix para promover el desarrollo colectivo de ideas sociales e innovadoras en un ambiente de diversi\u00f3n y adrenalina pura.", "register_link": "http://hackatrix.belatrixsf.com/", diff --git a/sample_data/idea_scores.json b/sample_data/idea_scores.json index abefa3a..d96026e 100644 --- a/sample_data/idea_scores.json +++ b/sample_data/idea_scores.json @@ -7,15 +7,6 @@ "category": 1, "value": 5 } -}, { - "model": "ideas.ideascores", - "pk": 2, - "fields": { - "idea": 1, - "jury": 5, - "category": 2, - "value": 5 - } }, { "model": "ideas.ideascores", "pk": 3, @@ -43,15 +34,6 @@ "category": 1, "value": 6 } -}, { - "model": "ideas.ideascores", - "pk": 6, - "fields": { - "idea": 1, - "jury": 8, - "category": 2, - "value": 3 - } }, { "model": "ideas.ideascores", "pk": 7, From e404fa571f6be025f0cd5cf1e128b202ecd73de0 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Mon, 21 May 2018 23:06:38 -0500 Subject: [PATCH 56/72] Separate messages from code in order to make them configurable through admin website --- BxEvents/settings/base.py | 19 +++++++++++++++++++ events/views.py | 3 ++- ideas/views.py | 33 ++++++++++++++++----------------- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/BxEvents/settings/base.py b/BxEvents/settings/base.py index 0bcf861..67cc9d4 100644 --- a/BxEvents/settings/base.py +++ b/BxEvents/settings/base.py @@ -1,3 +1,4 @@ +# encoding: utf-8 """ Django settings for BxEvents project. @@ -90,6 +91,24 @@ CONSTANCE_CONFIG = { 'TEAM_MIN_SIZE': (5, 'Mininum number of team members.', int), 'TEAM_MAX_SIZE': (8, 'Maximum number of team members.', int), + 'PARTICIPANT_REGISTERED': ('El participante ya está registrado', + 'Participant already registered', str), + 'IDEA_DELETED': ('Idea eliminada', 'Idea deletion message', str), + 'IDEA_EDIT_RESTRICTION': ('No puedes editar o borrar esta idea', + 'Idea edit or delete restriction', str), + 'IDEA_EXISTS': ('Esta idea ya existe', 'Idea already exists', str), + 'CANDIDATE_ALREADY': ('Ya se registró como candidato', + 'User already register as a candidate', str), + 'PARTICIPANT_IDEA_RESTRICTION': ('Ya se registro en una idea para este evento.', + 'Restriction message to avoid multiple registers for ideas in same event', str), + 'TEAM_MAX_SIZE_MESSAGE': ('Se alcanzó el número máximo de participantes por idea o ya está completo.', + 'Team max size reached', str), + 'TEAM_MIN_SIZE_MESSAGE': ('No se tiene el número mínimo de integrantes.', + 'Team min size not reached', str), + 'NOT_IDEA_OWNER': ('No eres el autor de la idea.', 'Not owner idea message', str), + 'USER_VOTED': ('Ya has registrado previamente tu voto', 'Message to user who already voted', str), + 'IDEA_EVALUATED': ('Esta idea ya fue evaluada en esta categoría', + 'Message to jury who already evaluate this idea in this category', str) } # Database diff --git a/events/views.py b/events/views.py index 3dcfca1..2831e73 100644 --- a/events/views.py +++ b/events/views.py @@ -1,3 +1,4 @@ +from constance import config from django.shortcuts import get_object_or_404 from rest_framework import status from rest_framework.decorators import api_view, permission_classes @@ -251,7 +252,7 @@ def register_attendance(request): Attendance.objects.create(meeting=meeting, participant=user) except Exception as e: print(e) - raise NotAcceptable('Participante ya registrado.') + raise NotAcceptable(config.PARTICIPANT_REGISTERED) idea_participant = IdeaParticipant.objects.filter(user=user) user_serializer = UserSerializer(user) diff --git a/ideas/views.py b/ideas/views.py index 411b1b9..e8b966b 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -1,4 +1,3 @@ -# encoding: utf-8 from django.shortcuts import get_object_or_404, get_list_or_404 from constance import config from rest_framework import status @@ -41,7 +40,7 @@ def idea(request, idea_id): if user == idea.author: if request.method == 'DELETE': idea.delete() - content = {'detail': 'Idea eliminada.'} + content = {'detail': config.IDEA_DELETED} return Response(content, status=status.HTTP_200_OK) if request.method == 'PATCH': serializer = IdeaUpdateSerializer(data=request.data) @@ -53,7 +52,7 @@ def idea(request, idea_id): serializer = IdeaSerializer(idea) return Response(serializer.data, status=status.HTTP_200_OK) else: - raise NotAcceptable('No puedes editar o borrar esta idea') + raise NotAcceptable(config.IDEA_EDIT_RESTRICTION) @api_view(['POST']) @@ -80,7 +79,7 @@ def idea_create(request): new_idea = Idea.objects.create(author=author, event=event, title=title, description=description) except Exception as e: print(e) - raise NotAcceptable('Esta idea ya existe.') + raise NotAcceptable(config.IDEA_EXISTS) serializer = IdeaSerializer(new_idea) return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -145,7 +144,7 @@ def idea_register_candidate(request, idea_id): IdeaCandidate.objects.create(idea=idea, user=user) except Exception as e: print(e) - raise NotAcceptable("Ya se registró como candidato.") + raise NotAcceptable(config.CANDIDATE_ALREADY) candidates = IdeaCandidate.objects.filter(idea=idea) serializer = IdeaCandidatesSerializer(candidates, many=True) return Response({"is_candidate": True, @@ -170,7 +169,7 @@ def idea_register(request, idea_id): if len(previous_records) > 0: for record in previous_records: if record.idea.event == idea.event: - raise NotAcceptable('Ya se registro en una idea para este evento.') + raise NotAcceptable(config.PARTICIPANT_IDEA_RESTRICTION) number_participants = IdeaParticipant.objects.filter(idea=idea).count() if config.TEAM_MAX_SIZE > number_participants and idea.is_completed is False: @@ -181,10 +180,10 @@ def idea_register(request, idea_id): idea.save() except Exception as e: print(e) - raise NotAcceptable('Ya registrado.') + raise NotAcceptable(config.PARTICIPANT_REGISTERED) else: raise ValidationError( - {'detail': 'Se alcanzó el número máximo de participantes por idea o ya está completo.'}) + {'detail': config.TEAM_MAX_SIZE_MESSAGE}) participants = IdeaParticipant.objects.filter(idea=idea) serializer = IdeaParticipantsSerializer(participants, many=True) return Response({"is_registered": True, @@ -217,9 +216,9 @@ def idea_candidate_approval(request, idea_id): candidate.is_accepted = True except Exception as e: print(e) - raise NotAcceptable("Ya se encuentra inscrito.") + raise NotAcceptable(config.PARTICIPANT_REGISTERED) else: - raise NotAcceptable("Alcanzó el número máximo por equipo.") + raise NotAcceptable(config.TEAM_MAX_SIZE_MESSAGE) candidate.save() participants = IdeaParticipant.objects.filter(idea=idea) @@ -232,7 +231,7 @@ def idea_candidate_approval(request, idea_id): serializer = IdeaParticipantsSerializer(participants, many=True) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) else: - raise NotAcceptable('No eres el autor de la idea.') + raise NotAcceptable(config.NOT_IDEA_OWNER) @api_view(['POST']) @@ -302,9 +301,9 @@ def idea_open(request, idea_id): idea.is_completed = False idea.save() else: - raise ValidationError({'detail': 'Número máximo de integrantes alcanzado.'}) + raise ValidationError({'detail': config.TEAM_MAX_SIZE_MESSAGE}) else: - raise ValidationError({'detail': 'No tienes permiso para marcar como abierto.'}) + raise ValidationError({'detail': config.NOT_IDEA_OWNER}) serializer = IdeaSerializer(idea) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) @@ -325,9 +324,9 @@ def idea_completed(request, idea_id): idea.is_completed = True idea.save() else: - raise ValidationError({'detail': 'No tienes el número mínimo de integrantes.'}) + raise ValidationError({'detail': config.TEAM_MIN_SIZE_MESSAGE}) else: - raise ValidationError({'detail': 'No tienes permiso para marcar como completado.'}) + raise ValidationError({'detail': config.NOT_IDEA_OWNER}) serializer = IdeaSerializer(idea) return Response(serializer.data, status=status.HTTP_202_ACCEPTED) @@ -392,7 +391,7 @@ def idea_vote(request, event_id): IdeaVotes.objects.create(event=event, idea=idea, participant=user) except Exception as e: print(e) - raise NotAcceptable('Usuario ya voto') + raise NotAcceptable(config.USER_VOTED) event_ideas = [] ideas = get_list_or_404(Idea, event=event_id, is_valid=True) for idea in ideas: @@ -464,7 +463,7 @@ def idea_rate(request, idea_id): IdeaScores.objects.create(idea=idea, jury=user, category=category, value=value) except Exception as e: print(e) - raise ValidationError({'detail': 'Ya fue evaluado en esta categoria.'}) + raise ValidationError({'detail': config.IDEA_EVALUATED}) if request.method == "PATCH": serializer = IdeaScoreSerializer(data=request.data) if serializer.is_valid(raise_exception=True): From ccd24ed50521eef8e713415d39211b2d1377372a Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Tue, 22 May 2018 19:58:57 -0500 Subject: [PATCH 57/72] Set max length to 9 for phone number serializer in user update endpoint --- participants/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/participants/serializers.py b/participants/serializers.py index 30cc991..c5fee74 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -34,7 +34,7 @@ class UserCreationSerializer(serializers.Serializer): class UserProfileSerializer(serializers.Serializer): full_name = serializers.CharField(max_length=255, required=False) - phone_number = serializers.CharField(required=False) + phone_number = serializers.CharField(max_length=9, required=False) role_id = serializers.IntegerField(required=False) From c2d2ad44b27f9007844088cf8d46415c194a6548 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 13:50:46 -0500 Subject: [PATCH 58/72] Add IsParticipant permission to idea vote endpoint --- ideas/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ideas/views.py b/ideas/views.py index e8b966b..8bd16fd 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -8,7 +8,7 @@ from events.models import Event from participants.models import User -from participants.permissions import IsJury, IsModerator +from participants.permissions import IsJury, IsModerator, IsParticipant from .models import Idea, IdeaVotes, IdeaScores, IdeaScoresCriteria from .models import IdeaCandidate, IdeaParticipant @@ -373,7 +373,7 @@ def my_ideas(request): @api_view(['GET', 'POST']) -@permission_classes((IsAuthenticatedOrReadOnly, )) +@permission_classes((IsAuthenticatedOrReadOnly, IsParticipant)) def idea_vote(request, event_id): """ Endpoint to vote for an idea From 06f009c49adc8fd7b33eddfd5d770f0a8dda515c Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 14:03:27 -0500 Subject: [PATCH 59/72] Add a Pipfile to define Python configurations --- Pipfile | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Pipfile diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..6f2f660 --- /dev/null +++ b/Pipfile @@ -0,0 +1,2 @@ +[requires] +python_full_version = "2.7" \ No newline at end of file From 7bada2254f18b67749a354d974740fb6c48bf414 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 14:06:01 -0500 Subject: [PATCH 60/72] Add specific Python version --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 6f2f660..cb5c30d 100644 --- a/Pipfile +++ b/Pipfile @@ -1,2 +1,2 @@ [requires] -python_full_version = "2.7" \ No newline at end of file +python_version = "2.7" \ No newline at end of file From fb68e9d40c4fba1f0cfa88bb2b83093e769bf25c Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 14:10:00 -0500 Subject: [PATCH 61/72] Add specific python full version --- Pipfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index cb5c30d..6c720c6 100644 --- a/Pipfile +++ b/Pipfile @@ -1,2 +1,2 @@ [requires] -python_version = "2.7" \ No newline at end of file +python_full_version = "2.7.15" \ No newline at end of file From 0999a871cb5c6f75b63e0bfa6a606f7d6972dd5b Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 14:13:04 -0500 Subject: [PATCH 62/72] Add Pipfile.lock to root project directory --- Pipfile.lock | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Pipfile.lock diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..565df73 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,20 @@ +{ + "_meta": { + "hash": { + "sha256": "073a83b7e278f1d87b7560ec97d04dd29ad3e7c612e25362f4d53a5e73a8bc58" + }, + "pipfile-spec": 6, + "requires": { + "python_full_version": "2.7.15" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": {} +} From 7f7833b6edeb8a3a93602d55a0b8b1cadeb66e0d Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 14:18:20 -0500 Subject: [PATCH 63/72] Delete Pipfile in order to rebuild deploy --- Pipfile | 2 -- Pipfile.lock | 20 -------------------- 2 files changed, 22 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 6c720c6..0000000 --- a/Pipfile +++ /dev/null @@ -1,2 +0,0 @@ -[requires] -python_full_version = "2.7.15" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 565df73..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,20 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "073a83b7e278f1d87b7560ec97d04dd29ad3e7c612e25362f4d53a5e73a8bc58" - }, - "pipfile-spec": 6, - "requires": { - "python_full_version": "2.7.15" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": {}, - "develop": {} -} From 2c091154e15de741674b6e8cd211a5e59ce6975d Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 22:46:21 -0500 Subject: [PATCH 64/72] Add IsAttende permission for idea vote endpoints. --- events/permissions.py | 12 ++++++++++++ ideas/views.py | 5 +++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 events/permissions.py diff --git a/events/permissions.py b/events/permissions.py new file mode 100644 index 0000000..7247691 --- /dev/null +++ b/events/permissions.py @@ -0,0 +1,12 @@ +from rest_framework import permissions + +from .models import Attendance + + +class IsAttendee(permissions.BasePermission): + message = 'Attendee restricted' + + def has_permission(self, request, view): + attendees = Attendance.objects.filter(participant__email=request.user.email).count() + if attendees > 0: + return True diff --git a/ideas/views.py b/ideas/views.py index 8bd16fd..a13358c 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -7,6 +7,7 @@ from rest_framework.response import Response from events.models import Event +from events.permissions import IsAttendee from participants.models import User from participants.permissions import IsJury, IsModerator, IsParticipant @@ -373,7 +374,7 @@ def my_ideas(request): @api_view(['GET', 'POST']) -@permission_classes((IsAuthenticatedOrReadOnly, IsParticipant)) +@permission_classes((IsAuthenticatedOrReadOnly, IsAttendee)) def idea_vote(request, event_id): """ Endpoint to vote for an idea @@ -393,7 +394,7 @@ def idea_vote(request, event_id): print(e) raise NotAcceptable(config.USER_VOTED) event_ideas = [] - ideas = get_list_or_404(Idea, event=event_id, is_valid=True) + ideas = get_list_or_404(Idea, event=event_id, is_valid=True, is_completed=True) for idea in ideas: votes = IdeaVotes.objects.filter(idea=idea).count() idea_response = {'id': idea.id, From 4e536584c6355dd193ff734c8d655e541703e652 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Fri, 25 May 2018 23:48:37 -0500 Subject: [PATCH 65/72] Remove IsModerator permission to get user list --- ideas/views.py | 2 +- participants/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ideas/views.py b/ideas/views.py index a13358c..398fc78 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -9,7 +9,7 @@ from events.models import Event from events.permissions import IsAttendee from participants.models import User -from participants.permissions import IsJury, IsModerator, IsParticipant +from participants.permissions import IsJury, IsModerator from .models import Idea, IdeaVotes, IdeaScores, IdeaScoresCriteria from .models import IdeaCandidate, IdeaParticipant diff --git a/participants/views.py b/participants/views.py index fd1d617..4d051d0 100644 --- a/participants/views.py +++ b/participants/views.py @@ -15,7 +15,7 @@ from rest_framework.response import Response from .models import User, Participant, Role -from .permissions import IsModerator, IsStaff +from .permissions import IsStaff from .serializers import UserSerializer, UserCreationSerializer from .serializers import UserUpdatePasswordSerializer, UserProfileSerializer, RoleSerializer from .serializers import EventProfileSerializer, IdeaProfileSerializer, AttendanceProfileSerializer @@ -40,7 +40,7 @@ def user_detail(request, user_id): @api_view(['GET']) -@permission_classes((IsAuthenticated, IsModerator)) +@permission_classes((IsAuthenticated, )) def user_list(request): """ Returns user list From 3bc69dad3e65a4d9960f237dee4df175f5739fac Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 26 May 2018 04:18:16 -0500 Subject: [PATCH 66/72] Add search fields for admin to participants, users and attendances --- events/admin.py | 1 + participants/admin.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/events/admin.py b/events/admin.py index 5036c2e..abb9ac8 100644 --- a/events/admin.py +++ b/events/admin.py @@ -29,6 +29,7 @@ class MeetingAdmin(admin.ModelAdmin): class AttendanceAdmin(admin.ModelAdmin): list_display = ('datetime', 'meeting', 'participant') + search_fields = ['participant__email'] admin.site.register(City, CityAdmin) diff --git a/participants/admin.py b/participants/admin.py index ef7fae6..f10cfec 100644 --- a/participants/admin.py +++ b/participants/admin.py @@ -27,6 +27,7 @@ def clean_password(self): class UserCustomAdmin(ImportExportMixin, BaseUserAdmin): form = UserChangeForm list_display = ("email", "full_name", "is_staff", "is_moderator", "is_jury", "is_password_reset_required") + search_fields = ['email', 'full_name'] fieldsets = ( (None, {'fields': ('email', 'password')}), ('Personal info', {'fields': ('first_name', @@ -58,6 +59,7 @@ class UserCustomAdmin(ImportExportMixin, BaseUserAdmin): class ParticipantForm(ImportExportMixin, admin.ModelAdmin): list_display = ("email", "full_name", "event_id") + search_fields = ['email', 'full_name'] admin.site.register(Participant, ParticipantForm) From 62f663b2930c16afbd9a0d5f8f816cec6bb707e5 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 26 May 2018 04:33:39 -0500 Subject: [PATCH 67/72] Change phone number validations in order to support multiple contact phone formats --- .../migrations/0002_auto_20180526_0430.py | 20 +++++++++++++++++++ participants/models.py | 3 ++- participants/serializers.py | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 participants/migrations/0002_auto_20180526_0430.py diff --git a/participants/migrations/0002_auto_20180526_0430.py b/participants/migrations/0002_auto_20180526_0430.py new file mode 100644 index 0000000..716e7aa --- /dev/null +++ b/participants/migrations/0002_auto_20180526_0430.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.8 on 2018-05-26 09:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('participants', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='phone_number', + field=models.CharField(blank=True, max_length=16, null=True), + ), + ] diff --git a/participants/models.py b/participants/models.py index 93853de..1088fb0 100644 --- a/participants/models.py +++ b/participants/models.py @@ -36,7 +36,8 @@ class User(AbstractBaseUser, PermissionsMixin): phone_regex = RegexValidator( regex=r'^\d{9}$', message="Phone number must be entered in the format: '999999999'. Up to 9 digits allowed.") - phone_number = models.CharField(validators=[phone_regex], max_length=9, blank=True, null=True) + # TODO: improve regexvalidator in order to support current contact mobile formats + phone_number = models.CharField(max_length=16, blank=True, null=True) role = models.ForeignKey(Role, blank=True, null=True) is_staff = models.BooleanField(_('is staff'), default=False) diff --git a/participants/serializers.py b/participants/serializers.py index c5fee74..b30cf45 100644 --- a/participants/serializers.py +++ b/participants/serializers.py @@ -34,7 +34,7 @@ class UserCreationSerializer(serializers.Serializer): class UserProfileSerializer(serializers.Serializer): full_name = serializers.CharField(max_length=255, required=False) - phone_number = serializers.CharField(max_length=9, required=False) + phone_number = serializers.CharField(max_length=16, required=False) role_id = serializers.IntegerField(required=False) From 71e1913be1e501c82c2207aca88b03f1f503c964 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 26 May 2018 13:58:00 -0500 Subject: [PATCH 68/72] Revert participant permissions on vote idea endpoint --- ideas/views.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ideas/views.py b/ideas/views.py index 398fc78..8bd16fd 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -7,9 +7,8 @@ from rest_framework.response import Response from events.models import Event -from events.permissions import IsAttendee from participants.models import User -from participants.permissions import IsJury, IsModerator +from participants.permissions import IsJury, IsModerator, IsParticipant from .models import Idea, IdeaVotes, IdeaScores, IdeaScoresCriteria from .models import IdeaCandidate, IdeaParticipant @@ -374,7 +373,7 @@ def my_ideas(request): @api_view(['GET', 'POST']) -@permission_classes((IsAuthenticatedOrReadOnly, IsAttendee)) +@permission_classes((IsAuthenticatedOrReadOnly, IsParticipant)) def idea_vote(request, event_id): """ Endpoint to vote for an idea @@ -394,7 +393,7 @@ def idea_vote(request, event_id): print(e) raise NotAcceptable(config.USER_VOTED) event_ideas = [] - ideas = get_list_or_404(Idea, event=event_id, is_valid=True, is_completed=True) + ideas = get_list_or_404(Idea, event=event_id, is_valid=True) for idea in ideas: votes = IdeaVotes.objects.filter(idea=idea).count() idea_response = {'id': idea.id, From e4e7e0deac97e0a86707d3cd9cfaa0b9bcf7df6c Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 26 May 2018 14:22:10 -0500 Subject: [PATCH 69/72] Add anonymous logic for idea vote lists --- events/permissions.py | 10 ++++++++++ ideas/views.py | 7 ++++--- participants/permissions.py | 10 ++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/events/permissions.py b/events/permissions.py index 7247691..d30947e 100644 --- a/events/permissions.py +++ b/events/permissions.py @@ -1,12 +1,22 @@ from rest_framework import permissions +from rest_framework.compat import is_authenticated from .models import Attendance +SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') + + class IsAttendee(permissions.BasePermission): message = 'Attendee restricted' def has_permission(self, request, view): + if request.user.is_anonymous: + return ( + request.method in SAFE_METHODS or + request.user and + is_authenticated(request.user) + ) attendees = Attendance.objects.filter(participant__email=request.user.email).count() if attendees > 0: return True diff --git a/ideas/views.py b/ideas/views.py index 8bd16fd..398fc78 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -7,8 +7,9 @@ from rest_framework.response import Response from events.models import Event +from events.permissions import IsAttendee from participants.models import User -from participants.permissions import IsJury, IsModerator, IsParticipant +from participants.permissions import IsJury, IsModerator from .models import Idea, IdeaVotes, IdeaScores, IdeaScoresCriteria from .models import IdeaCandidate, IdeaParticipant @@ -373,7 +374,7 @@ def my_ideas(request): @api_view(['GET', 'POST']) -@permission_classes((IsAuthenticatedOrReadOnly, IsParticipant)) +@permission_classes((IsAuthenticatedOrReadOnly, IsAttendee)) def idea_vote(request, event_id): """ Endpoint to vote for an idea @@ -393,7 +394,7 @@ def idea_vote(request, event_id): print(e) raise NotAcceptable(config.USER_VOTED) event_ideas = [] - ideas = get_list_or_404(Idea, event=event_id, is_valid=True) + ideas = get_list_or_404(Idea, event=event_id, is_valid=True, is_completed=True) for idea in ideas: votes = IdeaVotes.objects.filter(idea=idea).count() idea_response = {'id': idea.id, diff --git a/participants/permissions.py b/participants/permissions.py index 6e51663..a858bb8 100644 --- a/participants/permissions.py +++ b/participants/permissions.py @@ -1,8 +1,12 @@ from rest_framework import permissions +from rest_framework.compat import is_authenticated from .models import Participant +SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') + + class IsJury(permissions.BasePermission): message = 'Jury restricted' @@ -14,6 +18,12 @@ class IsParticipant(permissions.BasePermission): message = 'User needs to be a participant registered.' def has_permission(self, request, view): + if request.user.is_anonymous: + return ( + request.method in SAFE_METHODS or + request.user and + is_authenticated(request.user) + ) participants = Participant.objects.filter(email=request.user.email).count() if participants > 0: return True From cfef983124b71f0223686684b135f318cc93c177 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 26 May 2018 15:47:38 -0500 Subject: [PATCH 70/72] Add logic to avoid votes for idea participants for own ideas --- ideas/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ideas/views.py b/ideas/views.py index 398fc78..6801cb9 100644 --- a/ideas/views.py +++ b/ideas/views.py @@ -388,6 +388,9 @@ def idea_vote(request, event_id): idea = get_object_or_404(Idea, pk=serializer.validated_data['idea_id']) user = request.user event = get_object_or_404(Event, pk=event_id) + own_idea = IdeaParticipant.objects.filter(idea=idea, user=user).count() + if own_idea > 0: + raise NotAcceptable("No puedes votar por tu propia idea.") try: IdeaVotes.objects.create(event=event, idea=idea, participant=user) except Exception as e: From 46ba8962323fa4a02ef46d572d0d8ce89779e723 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 26 May 2018 16:05:21 -0500 Subject: [PATCH 71/72] HOTFIX: Unable idea vote url --- events/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/urls.py b/events/urls.py index 7526777..1dd83f9 100644 --- a/events/urls.py +++ b/events/urls.py @@ -8,7 +8,7 @@ urlpatterns = [ url(r'^(?P\d+)/$', event_detail, name='event_detail'), url(r'^(?P\d+)/idea/list/$', idea_list, name='idea_list'), - url(r'^(?P\d+)/idea/vote/$', idea_vote, name='idea_vote'), + # url(r'^(?P\d+)/idea/vote/$', idea_vote, name='idea_vote'), # url(r'^(?P\d+)/interaction/list/$', event_interaction, name='event_interaction'), url(r'^city/list/$', event_city_list, name='event_city_list'), url(r'^featured/$', event_featured, name='event_featured'), From a4f9c923077eccbb1c525b808675b4eaedf6a5f6 Mon Sep 17 00:00:00 2001 From: Sergio Infante Date: Sat, 26 May 2018 17:28:51 -0500 Subject: [PATCH 72/72] Revert "HOTFIX: Unable idea vote url" This reverts commit 46ba8962323fa4a02ef46d572d0d8ce89779e723. --- events/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events/urls.py b/events/urls.py index 1dd83f9..7526777 100644 --- a/events/urls.py +++ b/events/urls.py @@ -8,7 +8,7 @@ urlpatterns = [ url(r'^(?P\d+)/$', event_detail, name='event_detail'), url(r'^(?P\d+)/idea/list/$', idea_list, name='idea_list'), - # url(r'^(?P\d+)/idea/vote/$', idea_vote, name='idea_vote'), + url(r'^(?P\d+)/idea/vote/$', idea_vote, name='idea_vote'), # url(r'^(?P\d+)/interaction/list/$', event_interaction, name='event_interaction'), url(r'^city/list/$', event_city_list, name='event_city_list'), url(r'^featured/$', event_featured, name='event_featured'),
#TitleAuthor emailDescriptionIs Completed?Is Valid?IdeaJuryCategoryWeightValue