Skip to content
Snippets Groups Projects
models.py 6.75 KiB
Newer Older
  • Learn to ignore specific revisions
  • import tempfile
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    import uuid
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    from django.conf import settings
    
    from django.contrib.postgres.fields import JSONField
    
    from django.core.serializers.json import DjangoJSONEncoder
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    from django.db import models
    from django.utils import timezone
    
    
    from funkwhale_api.common import session
    
    from funkwhale_api.common import utils as common_utils
    
    from funkwhale_api.music import utils as music_utils
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    TYPE_CHOICES = [
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        ("Person", "Person"),
        ("Application", "Application"),
        ("Group", "Group"),
        ("Organization", "Organization"),
        ("Service", "Service"),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    ]
    
    
    class Actor(models.Model):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        ap_type = "Actor"
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        url = models.URLField(unique=True, max_length=500, db_index=True)
        outbox_url = models.URLField(max_length=500)
        inbox_url = models.URLField(max_length=500)
        following_url = models.URLField(max_length=500, null=True, blank=True)
        followers_url = models.URLField(max_length=500, null=True, blank=True)
        shared_inbox_url = models.URLField(max_length=500, null=True, blank=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        type = models.CharField(choices=TYPE_CHOICES, default="Person", max_length=25)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        name = models.CharField(max_length=200, null=True, blank=True)
        domain = models.CharField(max_length=1000)
        summary = models.CharField(max_length=500, null=True, blank=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        preferred_username = models.CharField(max_length=200, null=True, blank=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        public_key = models.CharField(max_length=5000, null=True, blank=True)
        private_key = models.CharField(max_length=5000, null=True, blank=True)
        creation_date = models.DateTimeField(default=timezone.now)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        last_fetch_date = models.DateTimeField(default=timezone.now)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        manually_approves_followers = models.NullBooleanField(default=None)
    
        followers = models.ManyToManyField(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            to="self",
    
            symmetrical=False,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            through="Follow",
            through_fields=("target", "actor"),
            related_name="following",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            unique_together = ["domain", "preferred_username"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        @property
        def webfinger_subject(self):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return "{}@{}".format(self.preferred_username, settings.FEDERATION_HOSTNAME)
    
    
        @property
        def private_key_id(self):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return "{}#main-key".format(self.url)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
        @property
        def mention_username(self):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return "@{}@{}".format(self.preferred_username, self.domain)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
        def save(self, **kwargs):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            lowercase_fields = ["domain"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            for field in lowercase_fields:
                v = getattr(self, field, None)
                if v:
                    setattr(self, field, v.lower())
    
            super().save(**kwargs)
    
        @property
        def is_local(self):
            return self.domain == settings.FEDERATION_HOSTNAME
    
    
        @property
        def is_system(self):
            from . import actors
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
            return all(
                [
                    settings.FEDERATION_HOSTNAME == self.domain,
                    self.preferred_username in actors.SYSTEM_ACTORS,
                ]
            )
    
    
        @property
        def system_conf(self):
            from . import actors
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
            if self.is_system:
                return actors.SYSTEM_ACTORS[self.preferred_username]
    
        def get_approved_followers(self):
            follows = self.received_follows.filter(approved=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return self.followers.filter(pk__in=follows.values_list("actor", flat=True))
    
    
    class Follow(models.Model):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        ap_type = "Follow"
    
        uuid = models.UUIDField(default=uuid.uuid4, unique=True)
        actor = models.ForeignKey(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            Actor, related_name="emitted_follows", on_delete=models.CASCADE
    
        )
        target = models.ForeignKey(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            Actor, related_name="received_follows", on_delete=models.CASCADE
    
        )
        creation_date = models.DateTimeField(default=timezone.now)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        modification_date = models.DateTimeField(auto_now=True)
    
        approved = models.NullBooleanField(default=None)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            unique_together = ["actor", "target"]
    
    
        def get_federation_url(self):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            return "{}#follows/{}".format(self.actor.url, self.uuid)
    
    class Library(models.Model):
        creation_date = models.DateTimeField(default=timezone.now)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        modification_date = models.DateTimeField(auto_now=True)
    
        fetched_date = models.DateTimeField(null=True, blank=True)
        actor = models.OneToOneField(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            Actor, on_delete=models.CASCADE, related_name="library"
        )
    
        uuid = models.UUIDField(default=uuid.uuid4)
    
        url = models.URLField(max_length=500)
    
        # use this flag to disable federation with a library
        federation_enabled = models.BooleanField()
        # should we mirror files locally or hotlink them?
        download_files = models.BooleanField()
    
        # should we automatically import new files from this library?
        autoimport = models.BooleanField()
        tracks_count = models.PositiveIntegerField(null=True, blank=True)
    
        follow = models.OneToOneField(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            Follow, related_name="library", null=True, blank=True, on_delete=models.SET_NULL
    
    get_file_path = common_utils.ChunkedPath("federation_cache")
    
        url = models.URLField(unique=True, max_length=500)
        audio_url = models.URLField(max_length=500)
    
        audio_mimetype = models.CharField(max_length=200)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        audio_file = models.FileField(upload_to=get_file_path, null=True, blank=True)
    
        creation_date = models.DateTimeField(default=timezone.now)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        modification_date = models.DateTimeField(auto_now=True)
    
        fetched_date = models.DateTimeField(null=True, blank=True)
        published_date = models.DateTimeField(null=True, blank=True)
        library = models.ForeignKey(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            Library, related_name="tracks", on_delete=models.CASCADE
        )
    
        artist_name = models.CharField(max_length=500)
        album_title = models.CharField(max_length=500)
        title = models.CharField(max_length=500)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        metadata = JSONField(default={}, max_length=10000, encoder=DjangoJSONEncoder)
    
    
        @property
        def mbid(self):
            try:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                return self.metadata["recording"]["musicbrainz_id"]
    
            except KeyError:
                pass
    
    
        def download_audio(self):
            from . import actors
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
            auth = actors.SYSTEM_ACTORS["library"].get_request_auth()
    
            remote_response = session.get_session().get(
                self.audio_url,
                auth=auth,
                stream=True,
                timeout=20,
                verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                headers={"Content-Type": "application/activity+json"},
    
            )
            with remote_response as r:
                remote_response.raise_for_status()
                extension = music_utils.get_ext_from_type(self.audio_mimetype)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                title = " - ".join([self.title, self.album_title, self.artist_name])
                filename = "{}.{}".format(title, extension)
    
                tmp_file = tempfile.TemporaryFile()
                for chunk in r.iter_content(chunk_size=512):
                    tmp_file.write(chunk)
                self.audio_file.save(filename, tmp_file)
    
    
        def get_metadata(self, key):
            return self.metadata.get(key)