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 %}
+
+
+ # |
+ Idea Title |
+ Participant Firstname |
+ Participant Lastname |
+ Participant email |
+
+ {% for register in participants_list %}
+
+ {{ forloop.counter }} |
+ {{ register.idea }} |
+ {{ register.user.first_name }} |
+ {{ register.user.last_name }} |
+ {{ register.user.email }} |
+
+ {% empty %}
+ no content. |
+ {% endfor %}
+
+ 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 %}
+
+
+ # |
+ Title |
+ Author email |
+ Description |
+ Is Completed? |
+ Is Valid? |
+
+ {% for register in idea_list %}
+
+ {{ forloop.counter }} |
+ {{ register.title }} |
+ {{ register.author.email }} |
+ {{ register.description }} |
+ {{ register.is_completed }} |
+ {{ register.is_valid }} |
+
+ {% empty %}
+ no content. |
+ {% endfor %}
+
+ 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
-
-
-
+
+
{% 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 %}
+
+
+ # |
+ Meeting Name |
+ Participant fullname |
+ Participant email |
+ Register datetime |
+
+ {% for register in attendance_list %}
+
+ {{ forloop.counter }} |
+ {{ register.meeting.name }} |
+ {{ register.participant.full_name }} |
+ {{ register.participant.email }} |
+ {{ register.datetime }} |
+
+ {% empty %}
+ no content. |
+ {% endfor %}
+
+ 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 %}
+
+
+ # |
+ Name |
+ Event |
+ Start date |
+ End date |
+ Is Over? |
+ Is Active? |
+
+ {% for register in meeting_list %}
+
+ {{ forloop.counter }} |
+ {{ register.name }} |
+ {{ register.event.title }} |
+ {{ register.start_date }} |
+ {{ register.end_date }} |
+ {{ register.is_over }} |
+ {{ register.is_active }} |
+
+ {% empty %}
+ no content. |
+ {% endfor %}
+
+
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
+
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 %}
+
+
+ # |
+ Idea Title |
+ Votes |
+
+ {% for register in results %}
+
+ {{ forloop.counter }} |
+ {{ register.idea__title }} |
+ {{ register.total }} |
+
+ {% empty %}
+ no content. |
+ {% endfor %}
+
+ 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 %}
+
+
+ # |
+ Title |
+ Author email |
+ Description |
+ Is Completed? |
+ Is Valid? |
+
+ {% for register in results %}
+
+ {{ forloop.counter }} |
+ {{ register.idea.title }} |
+ {{ register.jury.full_name }} |
+ {{ register.category }} |
+ {{ register.category.weight }} |
+ {{ register.value }} |
+
+ {% empty %}
+ no content. |
+ {% endfor %}
+
+ 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 }} |
+
+
+
+ Meetings Attendance |
+ # | Meeting | Assistants |
+ {% for register in attendances %}
+
+ {{ forloop.counter }} |
+ {{ register.meeting__name }} |
+ {{ register.total }} |
+
+ {% empty %}
+ no content. |
+ {% endfor %}
+
+
+ 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 @@
# |
- Title |
- Author email |
- Description |
- Is Completed? |
- Is Valid? |
+ Idea |
+ Jury |
+ Category |
+ Weight |
+ Value |
{% 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'),