UI Support

Part 1, Chapter 7


Up until now, we haven't had a reason to track users as drivers or riders. Users can be either. But as soon as we add a UI, we will need a way for users to sign up with a role. Drivers will see a different UI and will experience different functionality than riders.

The first thing we need to do is add support for user groups in our serializers in example/serializers.py.

Serializer

First, update the UserSerializer serializer:

# example/serializers.py
class UserSerializer(serializers.ModelSerializer):
    password1 = serializers.CharField(write_only=True)
    password2 = serializers.CharField(write_only=True)
    group = serializers.CharField()

    def validate(self, data):
        if data['password1'] != data['password2']:
            raise serializers.ValidationError('Passwords must match.')
        return data

    def create(self, validated_data):
        group_data = validated_data.pop('group')
        group, _ = Group.objects.get_or_create(name=group_data)
        data = {
            key: value for key, value in validated_data.items()
            if key not in ('password1', 'password2')
        }
        data['password'] = validated_data['password1']
        user = self.Meta.model.objects.create_user(**data)
        user.groups.add(group)
        user.save()
        return user

    class Meta:
        model = get_user_model()
        fields = (
            'id', 'username', 'password1', 'password2', 'first_name',
            'last_name', 'email', 'group',
        )
        read_only_fields = ('id',)

Add the import at the top, too.

from django.contrib.auth.models import Group

Model

Next, update the custom user model in example/models.py to support groups as well:

# example/models.py
class User(AbstractUser):
    @property
    def group(self):
        groups = self.groups.all()
        return groups[0].name if groups else None

We are not adding any database fields so we don't need to create a new migration.

View

Lastly, add the proper filters to the TripView in example/apis.py:

# example/apis.py
class TripView(viewsets.ReadOnlyModelViewSet):
    lookup_field = 'nk'
    lookup_url_kwarg = 'trip_nk'
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = TripSerializer

    def get_queryset(self):
        user = self.request.user
        if user.group == 'driver':
            return Trip.objects.filter(
                Q(status=Trip.REQUESTED) | Q(driver=user)
            )
        if user.group == 'rider':
            return Trip.objects.filter(rider=user)
        return Trip.objects.none()

Add the import at the top:

from django.db.models import Q

Test

Re-run the tests:

(env)$ python manage.py test example.tests

You should see a few errors:

rest_framework.exceptions.ValidationError:
{'rider': {'group': ['This field may not be null.']}}

...

AssertionError:
[OrderedDict([('id', 1), ('rider', None), [495 chars]')])] != []

...

AssertionError: 201 != 400

First, within example/tests/test_http.py, update the create_user() function to take an additional group_name parameter:

# example/tests/test_http.py
def create_user(username='[email protected]', password=PASSWORD, group_name='rider'):
    group, _ = Group.objects.get_or_create(name=group_name)
    user = get_user_model().objects.create_user(
        username=username, password=password)
    user.groups.add(group)
    user.save()
    return user

And add the group to the test_user_can_sign_up test in AuthenticationTest:

# example/tests/test_http.py
def test_user_can_sign_up(self):
    response = self.client.post(reverse('sign_up'), data={
        'username': '[email protected]',
        'first_name': 'Test',
        'last_name': 'User',
        'password1': PASSWORD,
        'password2': PASSWORD,
        'group': 'rider', # new
    })
    ...

Add the import:

from django.contrib.auth.models import Group

Run the tests again. You should see more passing.

Next, update HttpTripTest:

# example/tests/test_http.py
class HttpTripTest(APITestCase):
    def setUp(self):
        self.user = create_user()
        self.client = APIClient()
        self.client.login(username=self.user.username, password=PASSWORD)

    def test_user_can_list_trips(self):
        trips = [
            Trip.objects.create(
                pick_up_address='A', drop_off_address='B', rider=self.user),
            Trip.objects.create(
                pick_up_address='B', drop_off_address='C', rider=self.user),
            Trip.objects.create(
                pick_up_address='C', drop_off_address='D')
        ]
        response = self.client.get(reverse('trip:trip_list'))
        self.assertEqual(status.HTTP_200_OK, response.status_code)
        exp_trip_nks = [trip.nk for trip in trips[0:2]]
        act_trip_nks = [trip.get('nk') for trip in response.data]
        self.assertCountEqual(act_trip_nks, exp_trip_nks)

    def test_user_can_retrieve_trip_by_nk(self):
        trip = Trip.objects.create(
            pick_up_address='A', drop_off_address='B', rider=self.user)
        response = self.client.get(trip.get_absolute_url())
        self.assertEqual(status.HTTP_200_OK, response.status_code)
        self.assertEqual(trip.nk, response.data.get('nk'))

After modifications, all tests should pass:

(env)$ python manage.py test example.tests

Finally, run the server and test out the DRF Browsable API:

  1. http://localhost:8000/api/sign_up/
  2. http://localhost:8000/api/log_in/

drf sign up page




Mark as Completed