Esempio n. 1
0
File: util.py Progetto: timtomch/amy
def create_uploaded_persons_tasks(data):
    """
    Create persons and tasks from upload data.
    """

    # Quick sanity check.
    if any([row.get('errors') for row in data]):
        raise InternalError('Uploaded data contains errors, cancelling upload')

    persons_created = []
    tasks_created = []
    events = set()

    with transaction.atomic():
        for row in data:
            try:
                fields = {key: row[key] for key in Person.PERSON_UPLOAD_FIELDS}
                fields['username'] = create_username(row['personal'],
                                                     row['family'])
                if fields['email']:
                    # we should use existing Person or create one
                    p, created = Person.objects.get_or_create(
                        email__iexact=fields['email'], defaults=fields
                    )

                    if created:
                        persons_created.append(p)

                else:
                    # we should create a new Person without any email provided
                    p = Person(**fields)
                    p.save()
                    persons_created.append(p)

                if row['event'] and row['role']:
                    e = Event.objects.get(slug=row['event'])
                    r = Role.objects.get(name=row['role'])

                    # is the number of learners attending the event changed,
                    # we should update ``event.attendance``
                    if row['role'] == 'learner':
                        events.add(e)

                    t, created = Task.objects.get_or_create(person=p, event=e,
                                                            role=r)
                    if created:
                        tasks_created.append(t)

            except IntegrityError as e:
                raise IntegrityError('{0} (for {1})'.format(str(e), row))

            except ObjectDoesNotExist as e:
                raise ObjectDoesNotExist('{0} (for {1})'.format(str(e), row))

    for event in events:
        # if event.attendance is lower than number of learners, then
        # update the attendance
        update_event_attendance_from_tasks(event)

    return persons_created, tasks_created
Esempio n. 2
0
File: util.py Progetto: jstofel/amy
def create_uploaded_persons_tasks(data):
    """
    Create persons and tasks from upload data.
    """

    # Quick sanity check.
    if any([row.get('errors') for row in data]):
        raise InternalError('Uploaded data contains errors, cancelling upload')

    persons_created = []
    tasks_created = []
    events = set()

    with transaction.atomic():
        for row in data:
            try:
                fields = {key: row[key] for key in Person.PERSON_UPLOAD_FIELDS}
                fields['username'] = row['username']

                if fields['email']:
                    # we should use existing Person or create one
                    p, created = Person.objects.get_or_create(
                        email__iexact=fields['email'], defaults=fields
                    )

                    if created:
                        persons_created.append(p)

                else:
                    # we should create a new Person without any email provided
                    p = Person(**fields)
                    p.save()
                    persons_created.append(p)

                if row['event'] and row['role']:
                    e = Event.objects.get(slug=row['event'])
                    r = Role.objects.get(name=row['role'])

                    # is the number of learners attending the event changed,
                    # we should update ``event.attendance``
                    if row['role'] == 'learner':
                        events.add(e)

                    t, created = Task.objects.get_or_create(person=p, event=e,
                                                            role=r)
                    if created:
                        tasks_created.append(t)

            except IntegrityError as e:
                raise IntegrityError('{0} (for {1})'.format(str(e), row))

            except ObjectDoesNotExist as e:
                raise ObjectDoesNotExist('{0} (for {1})'.format(str(e), row))

    for event in events:
        # if event.attendance is lower than number of learners, then
        # update the attendance
        update_event_attendance_from_tasks(event)

    return persons_created, tasks_created
Esempio n. 3
0
def create_uploaded_persons_tasks(data, request=None):
    """
    Create persons and tasks from upload data.
    """

    # Quick sanity check.
    if any([row.get("errors") for row in data]):
        raise InternalError("Uploaded data contains errors, cancelling upload")

    persons_created = []
    tasks_created = []
    events = set()

    with transaction.atomic():
        for row in data:
            try:
                row_repr = ("{personal} {family} {username} <{email}>, "
                            "{role} at {event}").format(**row)

                fields = {key: row[key] for key in Person.PERSON_UPLOAD_FIELDS}
                fields["username"] = row["username"]

                if row["person_exists"] and row["existing_person_id"]:
                    # we should use existing Person
                    p = Person.objects.get(pk=row["existing_person_id"])

                elif row["person_exists"] and not row["existing_person_id"]:
                    # we should use existing Person
                    p = Person.objects.get(
                        personal=fields["personal"],
                        family=fields["family"],
                        username=fields["username"],
                        email=fields["email"],
                    )

                else:
                    # we should create a new Person without any email provided
                    p = Person(**fields)
                    p.save()
                    persons_created.append(p)

                if row["event"] and row["role"]:
                    e = Event.objects.get(slug=row["event"])
                    r = Role.objects.get(name=row["role"])

                    # if the number of learners attending the event changed,
                    # we should update ``event.attendance``
                    if row["role"] == "learner":
                        events.add(e)

                    t, created = Task.objects.get_or_create(person=p,
                                                            event=e,
                                                            role=r)
                    if created:
                        tasks_created.append(t)

            except IntegrityError as e:
                raise IntegrityError('{0} (for "{1}")'.format(
                    str(e), row_repr))

            except ObjectDoesNotExist as e:
                raise ObjectDoesNotExist('{0} (for "{1}")'.format(
                    str(e), row_repr))

    jobs_created = []
    rqjobs_created = []

    # for each created task, try to add a new-(supporting)-instructor action
    with transaction.atomic():
        for task in tasks_created:
            # conditions check out
            if NewInstructorAction.check(task):
                objs = dict(task=task, event=task.event)
                # prepare context and everything and create corresponding RQJob
                jobs, rqjobs = ActionManageMixin.add(
                    action_class=NewInstructorAction,
                    logger=logger,
                    scheduler=scheduler,
                    triggers=Trigger.objects.filter(active=True,
                                                    action="new-instructor"),
                    context_objects=objs,
                    object_=task,
                    request=request,
                )
                jobs_created += jobs
                rqjobs_created += rqjobs

            # conditions check out
            if NewSupportingInstructorAction.check(task):
                objs = dict(task=task, event=task.event)
                # prepare context and everything and create corresponding RQJob
                jobs, rqjobs = ActionManageMixin.add(
                    action_class=NewSupportingInstructorAction,
                    logger=logger,
                    scheduler=scheduler,
                    triggers=Trigger.objects.filter(
                        active=True, action="new-supporting-instructor"),
                    context_objects=objs,
                    object_=task,
                    request=request,
                )
                jobs_created += jobs
                rqjobs_created += rqjobs

    return persons_created, tasks_created
Esempio n. 4
0
def create_uploaded_persons_tasks(data):
    """
    Create persons and tasks from upload data.
    """

    # Quick sanity check.
    if any([row.get('errors') for row in data]):
        raise InternalError('Uploaded data contains errors, cancelling upload')

    persons_created = []
    tasks_created = []
    events = set()

    with transaction.atomic():
        for row in data:
            try:
                row_repr = ('{personal} {family} {username} <{email}>, '
                            '{role} at {event}').format(**row)

                fields = {key: row[key] for key in Person.PERSON_UPLOAD_FIELDS}
                fields['username'] = row['username']

                if row['person_exists'] and row['existing_person_id']:
                    # we should use existing Person
                    p = Person.objects.get(pk=row['existing_person_id'])

                elif row['person_exists'] and not row['existing_person_id']:
                    # we should use existing Person
                    p = Person.objects.get(
                        personal=fields['personal'], family=fields['family'],
                        username=fields['username'], email=fields['email'],
                    )

                else:
                    # we should create a new Person without any email provided
                    p = Person(**fields)
                    p.save()
                    persons_created.append(p)

                if row['event'] and row['role']:
                    e = Event.objects.get(slug=row['event'])
                    r = Role.objects.get(name=row['role'])

                    # if the number of learners attending the event changed,
                    # we should update ``event.attendance``
                    if row['role'] == 'learner':
                        events.add(e)

                    t, created = Task.objects.get_or_create(person=p, event=e,
                                                            role=r)
                    if created:
                        tasks_created.append(t)

            except IntegrityError as e:
                raise IntegrityError('{0} (for "{1}")'.format(str(e),
                                                              row_repr))

            except ObjectDoesNotExist as e:
                raise ObjectDoesNotExist('{0} (for "{1}")'.format(str(e),
                                                                  row_repr))

    return persons_created, tasks_created
Esempio n. 5
0
class TestExportingPersonData(BaseExportingTest):
    def setUp(self):
        # don't remove all badges
        # super().setUp()

        # prepare user
        self.user = Person(
            username="******",
            personal="User",
            family="Primary",
            email="*****@*****.**",
            is_active=True,
            data_privacy_agreement=True,
        )
        self.user.set_password('password')
        self.user.save()

        # save API endpoint URL
        self.url = reverse('api:export-person-data')

    def login(self):
        """Overwrite BaseExportingTest's login method: instead of loggin in
        as an admin, use a normal user."""
        self.client.login(username='******', password='******')

    def prepare_data(self, user):
        """Populate relational fields for the user."""

        # create and set airport for the user
        airport = Airport.objects.create(
            iata='DDD',
            fullname='Airport 55x105',
            country='CM',
            latitude=55.0,
            longitude=105.0,
        )
        self.user.airport = airport
        self.user.save()

        # create a fake organization
        test_host = Organization.objects.create(domain='example.com',
                                                fullname='Test Organization')

        # create an event that will later be used
        event = Event.objects.create(
            start=datetime.date(2018, 6, 16),
            end=datetime.date(2018, 6, 17),
            slug='2018-06-16-AMY-event',
            host=test_host,
            url='http://example.org/2018-06-16-AMY-event',
        )

        # add a role
        Role.objects.create(name='instructor', verbose_name='Instructor')

        # add an admin user
        self.setup_admin()

        # award user some badges via awards (intermediary model)
        # one badge was awarded for the event
        award1 = Award.objects.create(
            person=self.user,
            badge=Badge.objects.get(name='swc-instructor'),
            event=event,
            awarded=datetime.date(2018, 6, 16),
        )
        # second badge was awarded without any connected event
        award2 = Award.objects.create(
            person=self.user,
            badge=Badge.objects.get(name='dc-instructor'),
            awarded=datetime.date(2018, 6, 16),
        )

        # user took part in the event as an instructor
        self.user.task_set.create(
            event=event,
            role=Role.objects.get(name='instructor'),
        )

        # user knows a couple of languages
        self.user.languages.set(
            Language.objects.filter(name__in=['English', 'French']))

        # add training requests
        training_request = TrainingRequest.objects.create(
            # mixins
            data_privacy_agreement=True,
            code_of_conduct_agreement=True,
            state='p',  # pending
            person=self.user,
            group_name='Mosquitos',
            personal='User',
            middle='',
            family='Primary',
            email='*****@*****.**',
            github='primary_user',
            occupation='undisclosed',
            occupation_other='',
            affiliation='AMY',
            location='Worldwide',
            country='W3',
            underresourced=False,
            # need to set it below
            # domains=KnowledgeDomain.objects.first(),
            domains_other='E-commerce',
            underrepresented='',
            nonprofit_teaching_experience='Voluntary teacher',
            # need to set it below
            # previous_involvement=Role.objects.filter(name='instructor'),
            previous_training='course',
            previous_training_other='',
            previous_training_explanation='A course for voluntary teaching',
            previous_experience='ta',
            previous_experience_other='',
            previous_experience_explanation='After the course I became a TA',
            programming_language_usage_frequency='weekly',
            teaching_frequency_expectation='monthly',
            max_travelling_frequency='not-at-all',
            max_travelling_frequency_other='',
            reason='I want to became an instructor',
            comment='I like trains',
            training_completion_agreement=True,
            workshop_teaching_agreement=True,
            notes='Admin notes invisible to the user',
        )
        training_request.domains.set([KnowledgeDomain.objects.first()])
        training_request.previous_involvement.set(
            Role.objects.filter(name='instructor'))

        # add some training progress
        TrainingProgress.objects.create(
            trainee=self.user,
            requirement=TrainingRequirement.objects.get(name='Discussion'),
            state='p',  # passed
            event=event,
            evaluated_by=None,
            discarded=False,
            url=None,
        )
        TrainingProgress.objects.create(
            trainee=self.user,
            requirement=TrainingRequirement.objects.get(name='DC Homework'),
            state='f',  # failed
            event=None,
            evaluated_by=self.admin,
            discarded=False,
            url='http://example.org/homework',
        )

    def test_unauthorized_access(self):
        """Make sure only authenticated users can access."""
        # logout
        self.client.logout()

        # retrieve endpoint
        rv = self.client.get(self.url)

        # make sure it's inaccessible
        self.assertEqual(rv.status_code, 401)

    def test_only_for_one_user(self):
        """Make sure the results are available only for the logged-in user,
        no-one else."""
        # prepare a different user
        self.second_user = Person(
            username="******",
            personal="User",
            family="Secondary",
            email="*****@*****.**",
            is_active=True,
            data_privacy_agreement=True,
        )
        self.second_user.set_password('password')
        self.second_user.save()

        # login as first user
        self.client.login(username='******', password='******')

        # retrieve endpoint
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)
        # make sure this endpoint returns current user data
        self.assertEqual(rv.json()['username'], 'primary_user')

        # login as second user
        self.client.login(username='******', password='******')
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)
        # make sure this endpoint does not return first user data now
        self.assertEqual(rv.json()['username'], 'secondary_user')

    def test_all_related_objects_shown(self):
        """Test if all related fields are present in data output."""
        self.login()

        # retrieve endpoint
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)

        # API results parsed as JSON
        user_data = rv.json()
        user_data_keys = user_data.keys()

        # make sure these fields are NOT in the API output
        missing_fields = [
            'password',
            'is_active',
            'notes',
        ]

        # simple (non-relational) fields expected in API output
        expected_fields = [
            'data_privacy_agreement',
            'personal',
            'middle',
            'family',
            'email',
            'username',
            'gender',
            'may_contact',
            'publish_profile',
            'github',
            'twitter',
            'url',
            'user_notes',
            'affiliation',
            'occupation',
            'orcid',
        ]

        # relational fields expected in API output
        expected_relational = [
            'airport',
            'badges',
            'lessons',
            'domains',
            'languages',
            'tasks',  # renamed in serializer (was: task_set)
            'awards',  # renamed in serializer (was: award_set)
            'training_requests',  # renamed from "trainingrequest_set"
            'training_progresses',  # renamed from "trainingprogress_set"
        ]

        # ensure missing fields are not to be found in API output
        for field in missing_fields:
            self.assertNotIn(field, user_data_keys)

        # ensure required fields are present
        for field in expected_fields + expected_relational:
            self.assertIn(field, user_data_keys)

    def test_relational_fields_structure(self):
        """Make sure relational fields available via API endpoints
        retain a specific structure."""
        self.prepare_data(user=self.user)
        self.login()

        # retrieve endpoint
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)

        # API results parsed as JSON
        data = rv.json()

        # expected data dict
        expected = dict()

        # test expected Airport output
        expected['airport'] = {
            'iata': 'DDD',
            'fullname': 'Airport 55x105',
            'country': 'CM',
            'latitude': 55.0,
            'longitude': 105.0,
        }
        self.assertEqual(data['airport'], expected['airport'])

        # test expected Badges output
        expected['badges'] = [
            {
                'name':
                'swc-instructor',
                'title':
                'Software Carpentry Instructor',
                'criteria':
                'Teaching at Software Carpentry workshops or'
                ' online',
            },
            {
                'name': 'dc-instructor',
                'title': 'Data Carpentry Instructor',
                'criteria': 'Teaching at Data Carpentry workshops or'
                ' online',
            },
        ]
        self.assertEqual(data['badges'], expected['badges'])

        # test expected Awards output
        expected['awards'] = [
            {
                'badge': 'swc-instructor',
                'awarded': '2018-06-16',
                'event': {
                    'slug': '2018-06-16-AMY-event',
                    'start': '2018-06-16',
                    'end': '2018-06-17',
                    'tags': [],
                    'website_url': 'http://example.org/2018-06-16-AMY-event',
                    'venue': '',
                    'address': '',
                    'country': '',
                    'latitude': None,
                    'longitude': None,
                }
            },
            {
                'badge': 'dc-instructor',
                'awarded': '2018-06-16',
                'event': None,
            },
        ]
        self.assertEqual(data['awards'], expected['awards'])

        # test expected Tasks output
        expected['tasks'] = [
            {
                'event': {
                    'slug': '2018-06-16-AMY-event',
                    'start': '2018-06-16',
                    'end': '2018-06-17',
                    'tags': [],
                    'website_url': 'http://example.org/2018-06-16-AMY-event',
                    'venue': '',
                    'address': '',
                    'country': '',
                    'latitude': None,
                    'longitude': None,
                },
                'role': 'instructor',
            },
        ]
        self.assertEqual(data['tasks'], expected['tasks'])

        # test expected Languages output
        expected['languages'] = [
            'English',
            'French',
        ]
        self.assertEqual(data['languages'], expected['languages'])

        # test expected TrainingRequests output
        expected['training_requests'] = [{
            # these are generated by Django, so we borrow them from the
            # output
            'created_at':
            data['training_requests'][0]['created_at'],
            'last_updated_at':
            data['training_requests'][0]['last_updated_at'],
            'state':
            'Pending',
            'group_name':
            'Mosquitos',
            'personal':
            'User',
            'middle':
            '',
            'family':
            'Primary',
            'email':
            '*****@*****.**',
            'github':
            'primary_user',
            'occupation':
            'undisclosed',
            'occupation_other':
            '',
            'affiliation':
            'AMY',
            'location':
            'Worldwide',
            'country':
            'W3',
            'underresourced':
            False,
            'domains': ['Chemistry'],
            'domains_other':
            'E-commerce',
            'underrepresented':
            '',
            'nonprofit_teaching_experience':
            'Voluntary teacher',
            'previous_involvement': ['instructor'],
            'previous_training':
            'A certification or short course',
            'previous_training_other':
            '',
            'previous_training_explanation':
            'A course for voluntary teaching',
            'previous_experience':
            'Teaching assistant for a full course',
            'previous_experience_other':
            '',
            'previous_experience_explanation':
            'After the course I became a TA',
            'programming_language_usage_frequency':
            'A few times a week',
            'teaching_frequency_expectation':
            'Several times a year',
            'teaching_frequency_expectation_other':
            '',
            'max_travelling_frequency':
            'Not at all',
            'max_travelling_frequency_other':
            '',
            'reason':
            'I want to became an instructor',
            'comment':
            'I like trains',
            'training_completion_agreement':
            True,
            'workshop_teaching_agreement':
            True,
            'data_privacy_agreement':
            True,
            'code_of_conduct_agreement':
            True,
        }]

        self.assertEqual(len(data['training_requests']), 1)
        self.assertEqual(data['training_requests'][0],
                         expected['training_requests'][0])

        # test expected TrainingProgress output
        expected['training_progresses'] = [
            {
                # these are generated by Django, so we borrow them from the
                # output
                'created_at':
                data['training_progresses'][0]['created_at'],
                'last_updated_at':
                data['training_progresses'][0]['last_updated_at'],
                'requirement': {
                    'name': 'Discussion',
                    'url_required': False,
                    'event_required': False,
                },
                'state':
                'Passed',
                'discarded':
                False,
                'evaluated_by':
                None,
                'event': {
                    'slug': '2018-06-16-AMY-event',
                    'start': '2018-06-16',
                    'end': '2018-06-17',
                    'tags': [],
                    'website_url': 'http://example.org/2018-06-16-AMY-event',
                    'venue': '',
                    'address': '',
                    'country': '',
                    'latitude': None,
                    'longitude': None,
                },
                'url':
                None,
            },
            {
                # these are generated by Django, so we borrow them from the
                # output
                'created_at':
                data['training_progresses'][1]['created_at'],
                'last_updated_at':
                data['training_progresses'][1]['last_updated_at'],
                'requirement': {
                    'name': 'DC Homework',
                    'url_required': True,
                    'event_required': False,
                },
                'state':
                'Failed',
                'discarded':
                False,
                'evaluated_by': {
                    'name': 'Super User',
                },
                'event':
                None,
                'url':
                'http://example.org/homework',
            },
        ]
        self.assertEqual(len(data['training_progresses']), 2)
        self.assertEqual(data['training_progresses'][0],
                         expected['training_progresses'][0])
        self.assertEqual(data['training_progresses'][1],
                         expected['training_progresses'][1])
Esempio n. 6
0
def profileupdaterequest_accept(request, request_id, person_id=None):
    """
    Accept the profile update by rewriting values to selected user's profile.

    IMPORTANT: we do not rewrite all of the data users input (like
    other gender, or other lessons).  All of it is still in
    the database model ProfileUpdateRequest, but does not get written to the
    Person model object.
    """
    profileupdate = get_object_or_404(ProfileUpdateRequest, active=True,
                                      pk=request_id)
    airport = get_object_or_404(Airport,
                                iata__iexact=profileupdate.airport_iata)

    if person_id is None:
        person = Person()
        # since required perms change depending on `person_id`, we have to
        # check the perms programmatically; here user is required
        # `workshops.add_person` in order to add a new person
        if not request.user.has_perm('workshops.add_person'):
            raise PermissionDenied
    else:
        person = get_object_or_404(Person, pk=person_id)
        person_name = str(person)
        # since required perms change depending on `person_id`, we have to
        # check the perms programmatically; here user is required
        # `workshops.change_person` in order to set existing person's fields
        if not request.user.has_perm('workshops.change_person'):
            raise PermissionDenied

    person.personal = profileupdate.personal
    person.middle = profileupdate.middle
    person.family = profileupdate.family
    person.email = profileupdate.email
    person.affiliation = profileupdate.affiliation
    person.country = profileupdate.country
    person.airport = airport
    person.github = profileupdate.github
    person.twitter = profileupdate.twitter
    person.url = profileupdate.website
    # if occupation is "Other", simply save the `occupation_other` field,
    # otherwise get full display of occupation (since it's a choice field)
    if profileupdate.occupation == '':
        person.occupation = profileupdate.occupation_other
    else:
        person.occupation = profileupdate.get_occupation_display()
    person.orcid = profileupdate.orcid
    person.gender = profileupdate.gender
    person.user_notes = profileupdate.notes

    with transaction.atomic():
        # we need person to exist in the database in order to set domains and
        # lessons
        if not person.id:
            try:
                person.username = create_username(person.personal,
                                                  person.family)
                person.save()
            except IntegrityError:
                messages.error(
                    request,
                    'Cannot update profile: some database constraints weren\'t'
                    ' fulfilled. Make sure that user name, GitHub user name,'
                    ' Twitter user name, or email address are unique.'
                )
                return redirect(profileupdate.get_absolute_url())

        person.domains.set(list(profileupdate.domains.all()))
        person.languages.set(profileupdate.languages.all())

        try:
            person.save()
        except IntegrityError:
            messages.error(
                request,
                'Cannot update profile: some database constraints weren\'t'
                'fulfilled. Make sure that user name, GitHub user name,'
                'Twitter user name, or email address are unique.'
            )
            return redirect(profileupdate.get_absolute_url())

        # Since Person.lessons uses a intermediate model Qualification, we
        # ought to operate on Qualification objects instead of using
        # Person.lessons as a list.

        # erase old lessons
        Qualification.objects.filter(person=person).delete()
        # add new
        Qualification.objects.bulk_create([
            Qualification(person=person, lesson=L)
            for L in profileupdate.lessons.all()
        ])

        profileupdate.active = False
        profileupdate.save()

    if person_id is None:
        messages.success(request,
                         'New person was added successfully.')
    else:
        messages.success(request,
                         '{} was updated successfully.'.format(person_name))

    return redirect(person.get_absolute_url())
Esempio n. 7
0
def person_bulk_add_confirmation(request):
    """
    This view allows for manipulating and saving session-stored upload data.
    """
    persons_tasks = request.session.get('bulk-add-people')

    # if the session is empty, add message and redirect
    if not persons_tasks:
        messages.warning(
            request, "Could not locate CSV data, please try the upload again.")
        return redirect('person_bulk_add')

    if request.method == 'POST':
        # update values if user wants to change them
        personals = request.POST.getlist("personal")
        middles = request.POST.getlist("middle")
        families = request.POST.getlist("family")
        emails = request.POST.getlist("email")
        events = request.POST.getlist("event")
        roles = request.POST.getlist("role")
        data_update = zip(personals, middles, families, emails, events, roles)
        for k, record in enumerate(data_update):
            personal, middle, family, email, event, role = record
            persons_tasks[k]['person'] = {
                'personal': personal,
                'middle': middle,
                'family': family,
                'email': email
            }
            # when user wants to drop related event they will send empty string
            # so we should unconditionally accept new value for event even if
            # it's an empty string
            persons_tasks[k]['event'] = event
            persons_tasks[k]['role'] = role
            persons_tasks[k]['errors'] = None  # reset here

        # save updated data to the session
        request.session['bulk-add-people'] = persons_tasks

        # check if user wants to verify or save, or cancel

        if request.POST.get('verify', None):
            # if there's "verify" in POST, then do only verification
            any_errors = verify_upload_person_task(persons_tasks)
            if any_errors:
                messages.add_message(
                    request, messages.ERROR,
                    "Please make sure to fix all errors "
                    "listed below.")

            context = {
                'title': 'Confirm uploaded data',
                'persons_tasks': persons_tasks
            }
            return render(request, 'workshops/person_bulk_add_results.html',
                          context)

        elif (request.POST.get('confirm', None)
              and not request.POST.get('cancel', None)):
            # there must be "confirm" and no "cancel" in POST in order to save

            try:
                records = 0
                with transaction.atomic():
                    for row in persons_tasks:
                        # create person
                        p = Person(**row['person'])
                        p.save()
                        records += 1

                        # create task if data supplied
                        if row['event'] and row['role']:
                            e = Event.objects.get(slug=row['event'])
                            r = Role.objects.get(name=row['role'])
                            t = Task(person=p, event=e, role=r)
                            t.save()
                            records += 1

            except (IntegrityError, ObjectDoesNotExist) as e:
                messages.add_message(
                    request, messages.ERROR,
                    "Error saving data to the database: {}. "
                    "Please make sure to fix all errors "
                    "listed below.".format(e))
                verify_upload_person_task(persons_tasks)
                context = {
                    'title': 'Confirm uploaded data',
                    'persons_tasks': persons_tasks
                }
                return render(request,
                              'workshops/person_bulk_add_results.html',
                              context)

            else:
                request.session['bulk-add-people'] = None
                messages.add_message(
                    request, messages.SUCCESS,
                    "Successfully bulk-loaded {} records.".format(records))
                return redirect('person_bulk_add')

        else:
            # any "cancel" or no "confirm" in POST cancels the upload
            request.session['bulk-add-people'] = None
            return redirect('person_bulk_add')

    else:
        # alters persons_tasks via reference
        verify_upload_person_task(persons_tasks)

        context = {
            'title': 'Confirm uploaded data',
            'persons_tasks': persons_tasks
        }
        return render(request, 'workshops/person_bulk_add_results.html',
                      context)
Esempio n. 8
0
class TestExportingPersonData(BaseExportingTest):
    def setUp(self):
        # don't remove all badges
        # super().setUp()

        # prepare user
        self.user = Person(
            username="******", personal="User", family="Primary",
            email="*****@*****.**", is_active=True,
            data_privacy_agreement=True,
        )
        self.user.set_password('password')
        self.user.save()

        # save API endpoint URL
        self.url = reverse('api:export-person-data')

    def login(self):
        """Overwrite BaseExportingTest's login method: instead of loggin in
        as an admin, use a normal user."""
        self.client.login(username='******', password='******')

    def prepare_data(self, user):
        """Populate relational fields for the user."""

        # create and set airport for the user
        airport = Airport.objects.create(
            iata='DDD', fullname='Airport 55x105',
            country='CM',
            latitude=55.0, longitude=105.0,
        )
        self.user.airport = airport
        self.user.save()

        # create a fake organization
        test_host = Organization.objects.create(
            domain='example.com', fullname='Test Organization')

        # create an event that will later be used
        event = Event.objects.create(
            start=datetime.date(2018, 6, 16),
            end=datetime.date(2018, 6, 17),
            slug='2018-06-16-AMY-event',
            host=test_host,
            url='http://example.org/2018-06-16-AMY-event',
        )

        # add a role
        Role.objects.create(name='instructor', verbose_name='Instructor')

        # add an admin user
        self.setup_admin()

        # award user some badges via awards (intermediary model)
        # one badge was awarded for the event
        award1 = Award.objects.create(
            person=self.user,
            badge=Badge.objects.get(name='swc-instructor'),
            event=event,
            awarded=datetime.date(2018, 6, 16),
        )
        # second badge was awarded without any connected event
        award2 = Award.objects.create(
            person=self.user,
            badge=Badge.objects.get(name='dc-instructor'),
            awarded=datetime.date(2018, 6, 16),
        )

        # user took part in the event as an instructor
        self.user.task_set.create(
            event=event, role=Role.objects.get(name='instructor'),
        )

        # user knows a couple of languages
        self.user.languages.set(
            Language.objects.filter(name__in=['English', 'French'])
        )

        # add training requests
        training_request = TrainingRequest.objects.create(
            # mixins
            data_privacy_agreement=True,
            code_of_conduct_agreement=True,
            state='p',  # pending

            person=self.user,
            group_name='Mosquitos',
            personal='User',
            middle='',
            family='Primary',
            email='*****@*****.**',
            github='primary_user',
            occupation='undisclosed',
            occupation_other='',
            affiliation='AMY',
            location='Worldwide',
            country='W3',
            underresourced=False,
            # need to set it below
            # domains=KnowledgeDomain.objects.first(),
            domains_other='E-commerce',
            underrepresented='yes',
            underrepresented_details='LGBTQ',
            nonprofit_teaching_experience='Voluntary teacher',
            # need to set it below
            # previous_involvement=Role.objects.filter(name='instructor'),
            previous_training='course',
            previous_training_other='',
            previous_training_explanation='A course for voluntary teaching',
            previous_experience='ta',
            previous_experience_other='',
            previous_experience_explanation='After the course I became a TA',
            programming_language_usage_frequency='weekly',
            teaching_frequency_expectation='monthly',
            max_travelling_frequency='not-at-all',
            max_travelling_frequency_other='',
            reason='I want to became an instructor',
            user_notes='I like trains',
            training_completion_agreement=True,
            workshop_teaching_agreement=True,
        )
        training_request.domains.set([KnowledgeDomain.objects.first()])
        training_request.previous_involvement.set(
            Role.objects.filter(name='instructor'))

        # add some training progress
        TrainingProgress.objects.create(
            trainee=self.user,
            requirement=TrainingRequirement.objects.get(name='Discussion'),
            state='p',  # passed
            event=event,
            evaluated_by=None,
            discarded=False,
            url=None,
        )
        TrainingProgress.objects.create(
            trainee=self.user,
            requirement=TrainingRequirement.objects.get(name='DC Homework'),
            state='f',  # failed
            event=None,
            evaluated_by=self.admin,
            discarded=False,
            url='http://example.org/homework',
        )

    def test_unauthorized_access(self):
        """Make sure only authenticated users can access."""
        # logout
        self.client.logout()

        # retrieve endpoint
        rv = self.client.get(self.url)

        # make sure it's inaccessible
        self.assertEqual(rv.status_code, 401)

    def test_only_for_one_user(self):
        """Make sure the results are available only for the logged-in user,
        no-one else."""
        # prepare a different user
        self.second_user = Person(
            username="******", personal="User", family="Secondary",
            email="*****@*****.**", is_active=True,
            data_privacy_agreement=True,
        )
        self.second_user.set_password('password')
        self.second_user.save()

        # login as first user
        self.client.login(username='******', password='******')

        # retrieve endpoint
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)
        # make sure this endpoint returns current user data
        self.assertEqual(rv.json()['username'], 'primary_user')

        # login as second user
        self.client.login(username='******', password='******')
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)
        # make sure this endpoint does not return first user data now
        self.assertEqual(rv.json()['username'], 'secondary_user')

    def test_all_related_objects_shown(self):
        """Test if all related fields are present in data output."""
        self.login()

        # retrieve endpoint
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)

        # API results parsed as JSON
        user_data = rv.json()
        user_data_keys = user_data.keys()

        # make sure these fields are NOT in the API output
        missing_fields = [
            'password',
            'is_active',
        ]

        # simple (non-relational) fields expected in API output
        expected_fields = [
            'data_privacy_agreement',
            'personal',
            'middle',
            'family',
            'email',
            'username',
            'gender',
            'may_contact',
            'publish_profile',
            'github',
            'twitter',
            'url',
            'user_notes',
            'affiliation',
            'occupation',
            'orcid',
        ]

        # relational fields expected in API output
        expected_relational = [
            'airport',
            'badges',
            'lessons',
            'domains',
            'languages',
            'tasks',  # renamed in serializer (was: task_set)
            'awards',  # renamed in serializer (was: award_set)
            'training_requests',  # renamed from "trainingrequest_set"
            'training_progresses',  # renamed from "trainingprogress_set"
        ]

        # ensure missing fields are not to be found in API output
        for field in missing_fields:
            self.assertNotIn(field, user_data_keys)

        # ensure required fields are present
        for field in expected_fields + expected_relational:
            self.assertIn(field, user_data_keys)

    def test_relational_fields_structure(self):
        """Make sure relational fields available via API endpoints
        retain a specific structure."""
        self.prepare_data(user=self.user)
        self.login()

        # retrieve endpoint
        rv = self.client.get(self.url)
        self.assertEqual(rv.status_code, 200)

        # API results parsed as JSON
        data = rv.json()

        # expected data dict
        expected = dict()

        # test expected Airport output
        expected['airport'] = {
            'iata': 'DDD',
            'fullname': 'Airport 55x105',
            'country': 'CM',
            'latitude': 55.0,
            'longitude': 105.0,
        }
        self.assertEqual(data['airport'], expected['airport'])

        # test expected Badges output
        expected['badges'] = [
            {
                'name': 'swc-instructor',
                'title': 'Software Carpentry Instructor',
                'criteria': 'Teaching at Software Carpentry workshops or'
                            ' online',
            },
            {
                'name': 'dc-instructor',
                'title': 'Data Carpentry Instructor',
                'criteria': 'Teaching at Data Carpentry workshops or'
                            ' online',
            },
        ]
        self.assertEqual(data['badges'], expected['badges'])

        # test expected Awards output
        expected['awards'] = [
            {
                'badge': 'swc-instructor',
                'awarded': '2018-06-16',
                'event': {
                    'slug': '2018-06-16-AMY-event',
                    'start': '2018-06-16',
                    'end': '2018-06-17',
                    'tags': [],
                    'website_url': 'http://example.org/2018-06-16-AMY-event',
                    'venue': '',
                    'address': '',
                    'country': '',
                    'latitude': None,
                    'longitude': None,
                }
            },
            {
                'badge': 'dc-instructor',
                'awarded': '2018-06-16',
                'event': None,
            },
        ]
        self.assertEqual(data['awards'], expected['awards'])

        # test expected Tasks output
        expected['tasks'] = [
            {
                'event': {
                    'slug': '2018-06-16-AMY-event',
                    'start': '2018-06-16',
                    'end': '2018-06-17',
                    'tags': [],
                    'website_url': 'http://example.org/2018-06-16-AMY-event',
                    'venue': '',
                    'address': '',
                    'country': '',
                    'latitude': None,
                    'longitude': None,
                },
                'role': 'instructor',
            },
        ]
        self.assertEqual(data['tasks'], expected['tasks'])

        # test expected Languages output
        expected['languages'] = [
            'English',
            'French',
        ]
        self.assertEqual(data['languages'], expected['languages'])

        # test expected TrainingRequests output
        expected['training_requests'] = [
            {
                # these are generated by Django, so we borrow them from the
                # output
                'created_at': data['training_requests'][0]['created_at'],
                'last_updated_at':
                    data['training_requests'][0]['last_updated_at'],
                'state': 'Pending',
                'group_name': 'Mosquitos',
                'personal': 'User',
                'middle': '',
                'family': 'Primary',
                'email': '*****@*****.**',
                'github': 'primary_user',
                'occupation': 'undisclosed',
                'occupation_other': '',
                'affiliation': 'AMY',
                'location': 'Worldwide',
                'country': 'W3',
                'underresourced': False,
                'domains': ['Chemistry'],
                'domains_other': 'E-commerce',
                'underrepresented': 'yes',
                'underrepresented_details': 'LGBTQ',
                'nonprofit_teaching_experience': 'Voluntary teacher',
                'previous_involvement': ['instructor'],
                'previous_training': 'A certification or short course',
                'previous_training_other': '',
                'previous_training_explanation':
                    'A course for voluntary teaching',
                'previous_experience': 'Teaching assistant for a full course',
                'previous_experience_other': '',
                'previous_experience_explanation':
                    'After the course I became a TA',
                'programming_language_usage_frequency': 'A few times a week',
                'teaching_frequency_expectation': 'Several times a year',
                'teaching_frequency_expectation_other': '',
                'max_travelling_frequency': 'Not at all',
                'max_travelling_frequency_other': '',
                'reason': 'I want to became an instructor',
                'user_notes': 'I like trains',
                'training_completion_agreement': True,
                'workshop_teaching_agreement': True,
                'data_privacy_agreement': True,
                'code_of_conduct_agreement': True,
            }
        ]

        self.assertEqual(len(data['training_requests']), 1)
        self.assertEqual(data['training_requests'][0],
                         expected['training_requests'][0])

        # test expected TrainingProgress output
        expected['training_progresses'] = [
            {
                # these are generated by Django, so we borrow them from the
                # output
                'created_at': data['training_progresses'][0]['created_at'],
                'last_updated_at':
                    data['training_progresses'][0]['last_updated_at'],
                'requirement': {
                    'name': 'Discussion',
                    'url_required': False,
                    'event_required': False,
                },
                'state': 'Passed',
                'discarded': False,
                'evaluated_by': None,
                'event': {
                    'slug': '2018-06-16-AMY-event',
                    'start': '2018-06-16',
                    'end': '2018-06-17',
                    'tags': [],
                    'website_url': 'http://example.org/2018-06-16-AMY-event',
                    'venue': '',
                    'address': '',
                    'country': '',
                    'latitude': None,
                    'longitude': None,
                },
                'url': None,
            },
            {
                # these are generated by Django, so we borrow them from the
                # output
                'created_at': data['training_progresses'][1]['created_at'],
                'last_updated_at':
                    data['training_progresses'][1]['last_updated_at'],
                'requirement': {
                    'name': 'DC Homework',
                    'url_required': True,
                    'event_required': False,
                },
                'state': 'Failed',
                'discarded': False,
                'evaluated_by': {
                    'name': 'Super User',
                },
                'event': None,
                'url': 'http://example.org/homework',
            },
        ]
        self.assertEqual(len(data['training_progresses']), 2)
        self.assertEqual(data['training_progresses'][0],
                         expected['training_progresses'][0])
        self.assertEqual(data['training_progresses'][1],
                         expected['training_progresses'][1])