kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
				
				
				
			See #248: model / migration
							rodzic
							
								
									33b6db8dc1
								
							
						
					
					
						commit
						789bef38cb
					
				|  | @ -461,3 +461,7 @@ MUSIC_DIRECTORY_PATH = env("MUSIC_DIRECTORY_PATH", default=None) | |||
| MUSIC_DIRECTORY_SERVE_PATH = env( | ||||
|     "MUSIC_DIRECTORY_SERVE_PATH", default=MUSIC_DIRECTORY_PATH | ||||
| ) | ||||
| 
 | ||||
| USERS_INVITATION_EXPIRATION_DAYS = env.int( | ||||
|     "USERS_INVITATION_EXPIRATION_DAYS", default=14 | ||||
| ) | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| import factory | ||||
| from django.contrib.auth.models import Permission | ||||
| from django.utils import timezone | ||||
| 
 | ||||
| from funkwhale_api.factories import ManyToManyFromList, registry | ||||
| 
 | ||||
|  | @ -28,6 +29,17 @@ class GroupFactory(factory.django.DjangoModelFactory): | |||
|             self.permissions.add(*perms) | ||||
| 
 | ||||
| 
 | ||||
| @registry.register | ||||
| class InvitationFactory(factory.django.DjangoModelFactory): | ||||
|     owner = factory.LazyFunction(lambda: UserFactory()) | ||||
| 
 | ||||
|     class Meta: | ||||
|         model = "users.Invitation" | ||||
| 
 | ||||
|     class Params: | ||||
|         expired = factory.Trait(expiration_date=factory.LazyFunction(timezone.now)) | ||||
| 
 | ||||
| 
 | ||||
| @registry.register | ||||
| class UserFactory(factory.django.DjangoModelFactory): | ||||
|     username = factory.Sequence(lambda n: "user-{0}".format(n)) | ||||
|  | @ -40,6 +52,9 @@ class UserFactory(factory.django.DjangoModelFactory): | |||
|         model = "users.User" | ||||
|         django_get_or_create = ("username",) | ||||
| 
 | ||||
|     class Params: | ||||
|         invited = factory.Trait(invitation=factory.SubFactory(InvitationFactory)) | ||||
| 
 | ||||
|     @factory.post_generation | ||||
|     def perms(self, create, extracted, **kwargs): | ||||
|         if not create: | ||||
|  |  | |||
|  | @ -0,0 +1,31 @@ | |||
| # Generated by Django 2.0.6 on 2018-06-19 20:24 | ||||
| 
 | ||||
| from django.conf import settings | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| import django.utils.timezone | ||||
| 
 | ||||
| 
 | ||||
| class Migration(migrations.Migration): | ||||
| 
 | ||||
|     dependencies = [ | ||||
|         ('users', '0008_auto_20180617_1531'), | ||||
|     ] | ||||
| 
 | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='Invitation', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('creation_date', models.DateTimeField(default=django.utils.timezone.now)), | ||||
|                 ('expiration_date', models.DateTimeField()), | ||||
|                 ('code', models.CharField(max_length=50, unique=True)), | ||||
|                 ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invitations', to=settings.AUTH_USER_MODEL)), | ||||
|             ], | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='user', | ||||
|             name='invitation', | ||||
|             field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='users', to='users.Invitation'), | ||||
|         ), | ||||
|     ] | ||||
|  | @ -4,6 +4,8 @@ from __future__ import absolute_import, unicode_literals | |||
| import binascii | ||||
| import datetime | ||||
| import os | ||||
| import random | ||||
| import string | ||||
| import uuid | ||||
| 
 | ||||
| from django.conf import settings | ||||
|  | @ -79,6 +81,14 @@ class User(AbstractUser): | |||
| 
 | ||||
|     last_activity = models.DateTimeField(default=None, null=True, blank=True) | ||||
| 
 | ||||
|     invitation = models.ForeignKey( | ||||
|         "Invitation", | ||||
|         related_name="users", | ||||
|         null=True, | ||||
|         blank=True, | ||||
|         on_delete=models.SET_NULL, | ||||
|     ) | ||||
| 
 | ||||
|     def __str__(self): | ||||
|         return self.username | ||||
| 
 | ||||
|  | @ -138,3 +148,39 @@ class User(AbstractUser): | |||
|         if current is None or current < now - datetime.timedelta(seconds=delay): | ||||
|             self.last_activity = now | ||||
|             self.save(update_fields=["last_activity"]) | ||||
| 
 | ||||
| 
 | ||||
| def generate_code(length=10): | ||||
|     return "".join( | ||||
|         random.SystemRandom().choice(string.ascii_lowercase) for _ in range(length) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| class InvitationQuerySet(models.QuerySet): | ||||
|     def open(self): | ||||
|         now = timezone.now() | ||||
|         qs = self.annotate(_users=models.Count("users")) | ||||
|         qs = qs.filter(_users=0) | ||||
|         qs = qs.exclude(expiration_date__lte=now) | ||||
|         return qs | ||||
| 
 | ||||
| 
 | ||||
| class Invitation(models.Model): | ||||
|     creation_date = models.DateTimeField(default=timezone.now) | ||||
|     expiration_date = models.DateTimeField() | ||||
|     owner = models.ForeignKey( | ||||
|         User, related_name="invitations", on_delete=models.CASCADE | ||||
|     ) | ||||
|     code = models.CharField(max_length=50, unique=True) | ||||
| 
 | ||||
|     objects = InvitationQuerySet.as_manager() | ||||
| 
 | ||||
|     def save(self, **kwargs): | ||||
|         if not self.code: | ||||
|             self.code = generate_code() | ||||
|         if not self.expiration_date: | ||||
|             self.expiration_date = self.creation_date + datetime.timedelta( | ||||
|                 days=settings.USERS_INVITATION_EXPIRATION_DAYS | ||||
|             ) | ||||
| 
 | ||||
|         return super().save(**kwargs) | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| import datetime | ||||
| import pytest | ||||
| 
 | ||||
| from funkwhale_api.users import models | ||||
|  | @ -95,3 +96,25 @@ def test_record_activity_does_nothing_if_already(factories, now, mocker): | |||
|     user.record_activity() | ||||
| 
 | ||||
|     save.assert_not_called() | ||||
| 
 | ||||
| 
 | ||||
| def test_invitation_generates_random_code_on_save(factories): | ||||
|     invitation = factories["users.Invitation"]() | ||||
|     assert len(invitation.code) >= 6 | ||||
| 
 | ||||
| 
 | ||||
| def test_invitation_expires_after_delay(factories, settings): | ||||
|     delay = settings.USERS_INVITATION_EXPIRATION_DAYS | ||||
|     invitation = factories["users.Invitation"]() | ||||
|     assert invitation.expiration_date == ( | ||||
|         invitation.creation_date + datetime.timedelta(days=delay) | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def test_can_filter_open_invitations(factories): | ||||
|     okay = factories["users.Invitation"]() | ||||
|     factories["users.Invitation"](expired=True) | ||||
|     factories["users.User"](invited=True) | ||||
| 
 | ||||
|     assert models.Invitation.objects.count() == 3 | ||||
|     assert list(models.Invitation.objects.open()) == [okay] | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Eliot Berriot
						Eliot Berriot