2019-01-07 08:45:53 +00:00
|
|
|
import urllib.parse
|
|
|
|
import uuid
|
|
|
|
|
2019-08-29 12:12:26 +00:00
|
|
|
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
2019-08-22 09:30:30 +00:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
from django.contrib.postgres.fields import JSONField
|
2019-01-07 08:45:53 +00:00
|
|
|
from django.db import models
|
2019-08-26 12:47:01 +00:00
|
|
|
from django.db.models.signals import pre_save
|
|
|
|
from django.dispatch import receiver
|
2019-08-22 09:30:30 +00:00
|
|
|
from django.urls import reverse
|
2019-01-07 08:45:53 +00:00
|
|
|
from django.utils import timezone
|
|
|
|
|
2019-09-13 03:53:40 +00:00
|
|
|
from funkwhale_api.common import models as common_models
|
2019-08-22 09:30:30 +00:00
|
|
|
from funkwhale_api.federation import models as federation_models
|
|
|
|
from funkwhale_api.federation import utils as federation_utils
|
|
|
|
|
2019-08-26 13:27:21 +00:00
|
|
|
|
2019-01-07 08:45:53 +00:00
|
|
|
class InstancePolicyQuerySet(models.QuerySet):
|
|
|
|
def active(self):
|
|
|
|
return self.filter(is_active=True)
|
|
|
|
|
2019-01-09 16:52:14 +00:00
|
|
|
def matching_url(self, *urls):
|
|
|
|
if not urls:
|
|
|
|
return self.none()
|
|
|
|
query = None
|
|
|
|
for url in urls:
|
|
|
|
new_query = self.matching_url_query(url)
|
|
|
|
if query:
|
|
|
|
query = query | new_query
|
|
|
|
else:
|
|
|
|
query = new_query
|
|
|
|
return self.filter(query)
|
|
|
|
|
|
|
|
def matching_url_query(self, url):
|
2019-01-07 08:45:53 +00:00
|
|
|
parsed = urllib.parse.urlparse(url)
|
2019-01-09 16:52:14 +00:00
|
|
|
return models.Q(target_domain_id=parsed.hostname) | models.Q(
|
|
|
|
target_actor__fid=url
|
2019-01-07 08:45:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class InstancePolicy(models.Model):
|
|
|
|
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
|
|
|
actor = models.ForeignKey(
|
|
|
|
"federation.Actor",
|
|
|
|
related_name="created_instance_policies",
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
)
|
|
|
|
target_domain = models.OneToOneField(
|
|
|
|
"federation.Domain",
|
|
|
|
related_name="instance_policy",
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
)
|
|
|
|
target_actor = models.OneToOneField(
|
|
|
|
"federation.Actor",
|
|
|
|
related_name="instance_policy",
|
|
|
|
on_delete=models.CASCADE,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
)
|
|
|
|
creation_date = models.DateTimeField(default=timezone.now)
|
|
|
|
|
|
|
|
is_active = models.BooleanField(default=True)
|
|
|
|
# a summary explaining why the policy is in place
|
|
|
|
summary = models.TextField(max_length=10000, null=True, blank=True)
|
|
|
|
# either block everything (simpler, but less granularity)
|
|
|
|
block_all = models.BooleanField(default=False)
|
|
|
|
# or pick individual restrictions below
|
|
|
|
# do not show in timelines/notifications, except for actual followers
|
|
|
|
silence_activity = models.BooleanField(default=False)
|
|
|
|
silence_notifications = models.BooleanField(default=False)
|
|
|
|
# do not download any media from the target
|
|
|
|
reject_media = models.BooleanField(default=False)
|
|
|
|
|
|
|
|
objects = InstancePolicyQuerySet.as_manager()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def target(self):
|
|
|
|
if self.target_actor:
|
|
|
|
return {"type": "actor", "obj": self.target_actor}
|
|
|
|
if self.target_domain_id:
|
|
|
|
return {"type": "domain", "obj": self.target_domain}
|
2019-02-14 09:49:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
class UserFilter(models.Model):
|
|
|
|
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
|
|
|
creation_date = models.DateTimeField(default=timezone.now)
|
|
|
|
target_artist = models.ForeignKey(
|
|
|
|
"music.Artist", on_delete=models.CASCADE, related_name="user_filters"
|
|
|
|
)
|
|
|
|
user = models.ForeignKey(
|
|
|
|
"users.User", on_delete=models.CASCADE, related_name="content_filters"
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
unique_together = ("user", "target_artist")
|
|
|
|
|
|
|
|
@property
|
|
|
|
def target(self):
|
|
|
|
if self.target_artist:
|
|
|
|
return {"type": "artist", "obj": self.target_artist}
|
2019-08-22 09:30:30 +00:00
|
|
|
|
|
|
|
|
|
|
|
REPORT_TYPES = [
|
|
|
|
("takedown_request", "Takedown request"),
|
|
|
|
("invalid_metadata", "Invalid metadata"),
|
|
|
|
("illegal_content", "Illegal content"),
|
|
|
|
("offensive_content", "Offensive content"),
|
|
|
|
("other", "Other"),
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
class Report(federation_models.FederationMixin):
|
|
|
|
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
|
|
|
creation_date = models.DateTimeField(default=timezone.now)
|
2019-09-09 09:10:25 +00:00
|
|
|
summary = models.TextField(null=True, blank=True, max_length=50000)
|
2019-08-22 09:30:30 +00:00
|
|
|
handled_date = models.DateTimeField(null=True)
|
|
|
|
is_handled = models.BooleanField(default=False)
|
|
|
|
type = models.CharField(max_length=40, choices=REPORT_TYPES)
|
|
|
|
submitter_email = models.EmailField(null=True)
|
|
|
|
submitter = models.ForeignKey(
|
|
|
|
"federation.Actor",
|
|
|
|
related_name="reports",
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
assigned_to = models.ForeignKey(
|
|
|
|
"federation.Actor",
|
|
|
|
related_name="assigned_reports",
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
target_id = models.IntegerField(null=True)
|
|
|
|
target_content_type = models.ForeignKey(
|
|
|
|
ContentType, null=True, on_delete=models.CASCADE
|
|
|
|
)
|
|
|
|
target = GenericForeignKey("target_content_type", "target_id")
|
|
|
|
target_owner = models.ForeignKey(
|
|
|
|
"federation.Actor", on_delete=models.SET_NULL, null=True, blank=True
|
|
|
|
)
|
|
|
|
# frozen state of the target being reported, to ensure we still have info in the event of a
|
|
|
|
# delete
|
|
|
|
target_state = JSONField(null=True)
|
|
|
|
|
2019-08-29 12:12:26 +00:00
|
|
|
notes = GenericRelation(
|
2019-08-29 12:53:20 +00:00
|
|
|
"Note", content_type_field="target_content_type", object_id_field="target_id"
|
2019-08-29 12:12:26 +00:00
|
|
|
)
|
|
|
|
|
2019-09-13 03:53:40 +00:00
|
|
|
objects = common_models.GenericTargetQuerySet.as_manager()
|
|
|
|
|
2019-08-22 09:30:30 +00:00
|
|
|
def get_federation_id(self):
|
|
|
|
if self.fid:
|
|
|
|
return self.fid
|
|
|
|
|
|
|
|
return federation_utils.full_url(
|
|
|
|
reverse("federation:reports-detail", kwargs={"uuid": self.uuid})
|
|
|
|
)
|
|
|
|
|
|
|
|
def save(self, **kwargs):
|
|
|
|
if not self.pk and not self.fid:
|
|
|
|
self.fid = self.get_federation_id()
|
|
|
|
|
|
|
|
return super().save(**kwargs)
|
2019-08-26 12:47:01 +00:00
|
|
|
|
|
|
|
|
2019-08-29 09:45:41 +00:00
|
|
|
class Note(models.Model):
|
|
|
|
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
|
|
|
creation_date = models.DateTimeField(default=timezone.now)
|
|
|
|
summary = models.TextField(max_length=50000)
|
|
|
|
author = models.ForeignKey(
|
|
|
|
"federation.Actor", related_name="moderation_notes", on_delete=models.CASCADE
|
|
|
|
)
|
|
|
|
|
|
|
|
target_id = models.IntegerField(null=True)
|
|
|
|
target_content_type = models.ForeignKey(
|
|
|
|
ContentType, null=True, on_delete=models.CASCADE
|
|
|
|
)
|
|
|
|
target = GenericForeignKey("target_content_type", "target_id")
|
|
|
|
|
|
|
|
|
2019-08-26 12:47:01 +00:00
|
|
|
@receiver(pre_save, sender=Report)
|
|
|
|
def set_handled_date(sender, instance, **kwargs):
|
|
|
|
if instance.is_handled is True and not instance.handled_date:
|
|
|
|
instance.handled_date = timezone.now()
|
|
|
|
elif not instance.is_handled:
|
2019-08-26 13:27:21 +00:00
|
|
|
instance.handled_date = None
|