This commit is contained in:
Dustin J. Mitchell 2021-01-21 20:15:05 +00:00 коммит произвёл Dustin J. Mitchell
Родитель b74fbe4d0c
Коммит 26457f8b31
14 изменённых файлов: 50 добавлений и 26 удалений

Просмотреть файл

@ -31,6 +31,11 @@ tasks:
command: >- command: >-
pip install -r requirements.txt && pip install -r requirements.txt &&
python3 manage.py test python3 manage.py test
- name: Python code style
image: python:3.9
command: >-
pip install pycodestyle &&
pycodestyle ./mentoring
each(opts): each(opts):
provisionerId: 'proj-misc' provisionerId: 'proj-misc'
workerType: 'ci' workerType: 'ci'

Просмотреть файл

@ -8,6 +8,7 @@ from django.test import Client
from ..participants.models import Participant from ..participants.models import Participant
from .views import time_availability from .views import time_availability
class TimeAvailabilityTest(TestCase): class TimeAvailabilityTest(TestCase):
def test_no_avail(self): def test_no_avail(self):
@ -73,7 +74,7 @@ class PostTest(TestCase):
assert(p.full_name == 'Alex Doe') assert(p.full_name == 'Alex Doe')
assert(p.manager == 'Mana Jerr') assert(p.manager == 'Mana Jerr')
assert(p.manager_email == 'mjerr@mozilla.com') assert(p.manager_email == 'mjerr@mozilla.com')
assert(p.approved == None) assert(p.approved is None)
assert(p.time_availability == 'YYYYYYNNNYYYNNNNNNNNNNNN') assert(p.time_availability == 'YYYYYYNNNYYYNNNNNNNNNNNN')
assert(p.org == 'Firefox') assert(p.org == 'Firefox')
assert(p.org_level == 'P3') assert(p.org_level == 'P3')
@ -121,7 +122,7 @@ class PostTest(TestCase):
assert(p.full_name == 'Alex Doe') assert(p.full_name == 'Alex Doe')
assert(p.manager == 'Mana Jerr') assert(p.manager == 'Mana Jerr')
assert(p.manager_email == 'mjerr@mozilla.com') assert(p.manager_email == 'mjerr@mozilla.com')
assert(p.approved == None) assert(p.approved is None)
assert(p.time_availability == 'NNNNNNNNNYYYNNNNNNNNNNNN') assert(p.time_availability == 'NNNNNNNNNYYYNNNNNNNNNNNN')
assert(p.org == 'Firefox') assert(p.org == 'Firefox')
assert(p.org_level == 'P3') assert(p.org_level == 'P3')
@ -130,7 +131,7 @@ class PostTest(TestCase):
'Increasing Impact on Mozilla Mission', 'Increasing Impact on Mozilla Mission',
'Public Speaking', 'Public Speaking',
]) ])
assert(p.track_change == None) assert(p.track_change is None)
assert(p.org_chart_distance == 'Prefer distant') assert(p.org_chart_distance == 'Prefer distant')
assert(p.comments == 'asdf') assert(p.comments == 'asdf')

Просмотреть файл

@ -14,6 +14,7 @@ from ..participants.models import Participant
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def time_availability(time_availability): def time_availability(time_availability):
'''Parse a list of 'xx:00 - xx:00 UTC' strings into the 24-hour format in the model''' '''Parse a list of 'xx:00 - xx:00 UTC' strings into the 24-hour format in the model'''
rv = ['N'] * 24 rv = ['N'] * 24
@ -61,7 +62,6 @@ def parse_form(form):
) )
@require_http_methods(["POST"]) @require_http_methods(["POST"])
@csrf_exempt @csrf_exempt
def webhook(request): def webhook(request):

Просмотреть файл

@ -1,6 +1,7 @@
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
# Check that a user is authenticated; this automatically redirects un-authenticated # Check that a user is authenticated; this automatically redirects un-authenticated
# users to the SSO login page (and right back if auto-login is enabled) # users to the SSO login page (and right back if auto-login is enabled)
@user_passes_test(lambda user: user.is_authenticated) @user_passes_test(lambda user: user.is_authenticated)

Просмотреть файл

@ -2,12 +2,16 @@ from django.contrib import admin
from .models import Pair, HistoricalPair from .models import Pair, HistoricalPair
class PairAdmin(admin.ModelAdmin): class PairAdmin(admin.ModelAdmin):
list_display = ('mentor', 'learner', 'pair_id') list_display = ('mentor', 'learner', 'pair_id')
admin.site.register(Pair, PairAdmin) admin.site.register(Pair, PairAdmin)
class HistoricalPairAdmin(admin.ModelAdmin): class HistoricalPairAdmin(admin.ModelAdmin):
list_display = ('pair_id',) list_display = ('pair_id',)
admin.site.register(HistoricalPair, HistoricalPairAdmin) admin.site.register(HistoricalPair, HistoricalPairAdmin)

Просмотреть файл

@ -8,6 +8,7 @@ from django.db import models
from ..participants.models import Participant from ..participants.models import Participant
class Pair(models.Model): class Pair(models.Model):
""" """
An active pairing in the program. An active pairing in the program.
@ -31,7 +32,7 @@ class Pair(models.Model):
start_date = models.DateTimeField( start_date = models.DateTimeField(
null=False, null=False,
default=lambda: datetime.datetime.now(pytz.UTC), default=lambda: datetime.datetime.now(pytz.UTC),
help_text=dedent('''Date this pairing began''') help_text=dedent('''Date this pairing began''')
) )

Просмотреть файл

@ -3,6 +3,7 @@ from rest_framework import serializers, viewsets, permissions, mixins
from .models import Pair from .models import Pair
from ..participants.models import Participant from ..participants.models import Participant
class PairSerializer(serializers.HyperlinkedModelSerializer): class PairSerializer(serializers.HyperlinkedModelSerializer):
mentor = serializers.PrimaryKeyRelatedField( mentor = serializers.PrimaryKeyRelatedField(
queryset=Participant.objects.all().filter(role=Participant.MENTOR)) queryset=Participant.objects.all().filter(role=Participant.MENTOR))

Просмотреть файл

@ -8,10 +8,11 @@ from rest_framework.test import APIClient
from .models import Pair, HistoricalPair from .models import Pair, HistoricalPair
from ..participants.models import Participant from ..participants.models import Participant
class PairTest(TestCase): class PairTest(TestCase):
def make_particips(self): def make_particips(self):
l = Participant( learner = Participant(
expires=datetime.datetime.now(pytz.UTC), expires=datetime.datetime.now(pytz.UTC),
email='llearner@mozilla.com', email='llearner@mozilla.com',
role=Participant.LEARNER, role=Participant.LEARNER,
@ -20,9 +21,9 @@ class PairTest(TestCase):
manager_email='mshur@mozilla.com', manager_email='mshur@mozilla.com',
time_availability='N' * 24, time_availability='N' * 24,
) )
l.save() learner.save()
m = Participant( mentor = Participant(
expires=datetime.datetime.now(pytz.UTC), expires=datetime.datetime.now(pytz.UTC),
email='mmentor@mozilla.com', email='mmentor@mozilla.com',
role=Participant.MENTOR, role=Participant.MENTOR,
@ -31,13 +32,13 @@ class PairTest(TestCase):
manager_email='mshur@mozilla.com', manager_email='mshur@mozilla.com',
time_availability='N' * 24, time_availability='N' * 24,
) )
m.save() mentor.save()
return l, m return learner, mentor
def test_model_make_pair(self): def test_model_make_pair(self):
l, m = self.make_particips() learner, mentor = self.make_particips()
p = Pair(learner=l, mentor=m) p = Pair(learner=learner, mentor=mentor)
p.save() p.save()
self.assertEqual(p.learner.email, 'llearner@mozilla.com') self.assertEqual(p.learner.email, 'llearner@mozilla.com')
@ -53,7 +54,7 @@ class PairTest(TestCase):
self.assertTrue(HistoricalPair.already_paired(p)) self.assertTrue(HistoricalPair.already_paired(p))
def test_make_pair_rest_mentor_as_learner(self): def test_make_pair_rest_mentor_as_learner(self):
l, m = self.make_particips() learner, mentor = self.make_particips()
client = APIClient() client = APIClient()
user = User.objects.create_superuser('test') user = User.objects.create_superuser('test')
@ -62,6 +63,6 @@ class PairTest(TestCase):
res = client.post( res = client.post(
'/api/pairs', '/api/pairs',
# note that these are reversed # note that these are reversed
{'mentor': l.id, 'learner': m.id}, {'mentor': learner.id, 'learner': mentor.id},
format='json') format='json')
self.assertEqual(res.status_code, 400) self.assertEqual(res.status_code, 400)

Просмотреть файл

@ -14,7 +14,7 @@ def validate_time_availability(time_availability):
def validate_interests(interests): def validate_interests(interests):
if type(interests) != list: if type(interests) != list:
raise ValidationError('interests must be a list') raise ValidationError('interests must be a list')
if any(type(i) != type('') for i in interests): if any(not isinstance(i, str) for i in interests):
raise ValidationError('interests must contain strings') raise ValidationError('interests must contain strings')
@ -45,7 +45,7 @@ class Participant(models.Model):
help_text=dedent('''\ help_text=dedent('''\
The participant's role in the program. Note that the same email may appear The participant's role in the program. Note that the same email may appear
at most once in each role.'''), at most once in each role.'''),
) )
full_name = models.CharField(null=False, max_length=512, help_text=dedent('''\ full_name = models.CharField(null=False, max_length=512, help_text=dedent('''\
The participant's full name (as they would prefer to be called).''')) The participant's full name (as they would prefer to be called).'''))
@ -67,7 +67,7 @@ class Participant(models.Model):
help_text=dedent('''\ help_text=dedent('''\
The participant's time availability, as a sequence of Y and N for each UTC hour, The participant's time availability, as a sequence of Y and N for each UTC hour,
so `NNNYYYYYYYYYNNNNNNNNNNNN` indicates availability from 03:00-12:00 UTC.'''), so `NNNYYYYYYYYYNNNNNNNNNNNN` indicates availability from 03:00-12:00 UTC.'''),
) )
org = models.CharField(max_length=100, null=True, help_text=dedent('''\ org = models.CharField(max_length=100, null=True, help_text=dedent('''\
Participant's organization (roughly, executive to whom they report)''')) Participant's organization (roughly, executive to whom they report)'''))
@ -78,11 +78,14 @@ class Participant(models.Model):
time_at_org_level = models.CharField(max_length=10, null=True, help_text=dedent('''\ time_at_org_level = models.CharField(max_length=10, null=True, help_text=dedent('''\
Participant's time at current organizational level, e.g., `2-3 y`''')) Participant's time at current organizational level, e.g., `2-3 y`'''))
interests = models.JSONField(null=True, blank=False, help_text=dedent('''\ interests = models.JSONField(
A learner's areas of interest, or a mentor's areas in which they can offer mentorship; null=True,
format is an array of open-text strings.'''), blank=False,
help_text=dedent('''\
A learner's areas of interest, or a mentor's areas in which they can offer mentorship;
format is an array of open-text strings.'''),
validators=[validate_interests], validators=[validate_interests],
) )
track_change = models.CharField(null=True, max_length=64, help_text=dedent('''\ track_change = models.CharField(null=True, max_length=64, help_text=dedent('''\
Whether the participant is interested in changing tracks (between IC and Manager)''')) Whether the participant is interested in changing tracks (between IC and Manager)'''))
@ -92,13 +95,13 @@ class Participant(models.Model):
null=True, null=True,
blank=False, blank=False,
help_text=dedent('''Preference for a pairing nearby or distant in the org chart (open text)''') help_text=dedent('''Preference for a pairing nearby or distant in the org chart (open text)''')
) )
comments = models.TextField( comments = models.TextField(
null=False, null=False,
blank=True, blank=True,
help_text=dedent('''Open comments from the participant's enrollment'''), help_text=dedent('''Open comments from the participant's enrollment'''),
) )
class Meta: class Meta:
db_table = "participants" db_table = "participants"

Просмотреть файл

@ -2,6 +2,7 @@ from rest_framework import serializers, viewsets, permissions
from .models import Participant from .models import Participant
class ParticipantSerializer(serializers.HyperlinkedModelSerializer): class ParticipantSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = Participant model = Participant

Просмотреть файл

@ -5,6 +5,7 @@ from configurations import Configuration, values
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
class Base(Configuration): class Base(Configuration):
# Application definition # Application definition
@ -98,6 +99,7 @@ class Base(Configuration):
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / "static"] STATICFILES_DIRS = [BASE_DIR / "static"]
class Production(Base): class Production(Base):
DEBUG = False DEBUG = False

Просмотреть файл

@ -4,6 +4,7 @@ from django.test import TestCase
from .auth import MentoringAuthBackend from .auth import MentoringAuthBackend
class Auth(TestCase): class Auth(TestCase):
def get_username(self): def get_username(self):

Просмотреть файл

@ -16,7 +16,7 @@ urlpatterns = [
# if (and only if) we are in DEBUG mode (meaning Development), we allow # if (and only if) we are in DEBUG mode (meaning Development), we allow
# users to sign in using simple Django auth # users to sign in using simple Django auth
] + ([path('accounts/', include('django.contrib.auth.urls'))] if settings.DEBUG else []) + [ ] + ([path('accounts/', include('django.contrib.auth.urls'))] if settings.DEBUG else []) + [
# ..and anything else renders the frontend # ..and anything else renders the frontend
path('', include('mentoring.frontend.urls')), path('', include('mentoring.frontend.urls')),

3
setup.cfg Normal file
Просмотреть файл

@ -0,0 +1,3 @@
[pycodestyle]
# E501 = line length
ignore = E501