Anna Sirota
caae613747
* removes all soft-deletion; * shows a "Delete extension" button on the draft page in case it can be deleted; * shows a "Delete version" button on the version page in case it can be deleted; * a version can be deleted if * its file isn't approved, and it doesn't have any ratings; * an extension can be deleted if * it's not listed, and doesn't have any ratings or abuse reports; * all it's versions can also be deleted; * changes default `File.status` from `APPROVED` to `AWAITING_REVIEW` With version's file status being `APPROVED` by default, a version can never be deleted, even when the extension is still a draft. This change doesn't affect the approval process because * when an extension is approved its latest version becomes approved automatically (no change here); * when a new version is uploaded to an approved extension, it's approved automatically (this is new). This allows authors to delete their drafts, freeing the extension slug and making it possible to re-upload the same file. This also makes it possible to easily fix mistakes during the drafting of a new extension (e.g. delete a version and re-upload it without bumping a version for each typo/mistake in packaging and so on). (see #78 and #63) Reviewed-on: #81
112 lines
4.0 KiB
Python
112 lines
4.0 KiB
Python
from django import forms
|
|
from django.contrib.auth import get_user_model
|
|
from django.contrib.gis.geoip2 import GeoIP2, GeoIP2Exception
|
|
from django.core.validators import validate_ipv46_address
|
|
from django.db import models
|
|
from django.urls import reverse
|
|
|
|
from extended_choices import Choices
|
|
from geoip2.errors import GeoIP2Error
|
|
|
|
from constants.base import ABUSE_TYPE, ABUSE_TYPE_EXTENSION, ABUSE_TYPE_RATING
|
|
from common.model_mixins import CreatedModifiedMixin, TrackChangesMixin
|
|
import extensions.fields
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class AbuseReport(CreatedModifiedMixin, TrackChangesMixin, models.Model):
|
|
TYPE = ABUSE_TYPE
|
|
|
|
REASONS = Choices(
|
|
('OTHER', 127, 'Other'),
|
|
('DAMAGE', 1, 'Damages computer and/or data'),
|
|
('SPAM', 2, 'Creates spam or advertising'),
|
|
('BROKEN', 3, "Doesn't work, breaks Blender, or slows it down"),
|
|
('POLICY', 4, 'Hateful, violent, or illegal content'),
|
|
('DECEPTIVE', 5, "Pretends to be something it's not"),
|
|
)
|
|
|
|
STATUSES = Choices(
|
|
('UNTRIAGED', 1, 'Untriaged'),
|
|
('VALID', 2, 'Valid'),
|
|
('SUSPICIOUS', 3, 'Suspicious'),
|
|
)
|
|
|
|
# NULL if the reporter is anonymous.
|
|
# FIXME? make non-null
|
|
reporter = models.ForeignKey(
|
|
User,
|
|
null=True,
|
|
blank=True,
|
|
related_name='abuse_reported',
|
|
on_delete=models.SET_NULL,
|
|
)
|
|
# An abuse report can be for an extension or a user.
|
|
# If user is set then extension should be null.
|
|
# If extension is null then user should be set.
|
|
user = models.ForeignKey(
|
|
User, null=True, related_name='abuse_reports', on_delete=models.SET_NULL
|
|
)
|
|
extension = models.ForeignKey(
|
|
'extensions.Extension', blank=True, null=True, on_delete=models.CASCADE
|
|
)
|
|
extension_version = extensions.fields.VersionStringField(max_length=64, null=True, blank=True)
|
|
rating = models.ForeignKey('ratings.Rating', blank=True, null=True, on_delete=models.CASCADE)
|
|
message = models.TextField(blank=True)
|
|
reason = models.PositiveSmallIntegerField(
|
|
default=REASONS[0][1], choices=REASONS.choices, blank=False, null=False
|
|
)
|
|
version = extensions.fields.VersionStringField(
|
|
max_length=64,
|
|
null=True,
|
|
blank=True,
|
|
help_text=('Version of Blender affected by this report, if applicable.'),
|
|
)
|
|
type = models.PositiveSmallIntegerField(
|
|
default=ABUSE_TYPE_EXTENSION, choices=TYPE.choices, blank=False, null=False
|
|
)
|
|
|
|
status = models.PositiveSmallIntegerField(default=STATUSES.UNTRIAGED, choices=STATUSES.choices)
|
|
|
|
@classmethod
|
|
def lookup_country_code_from_ip(cls, ip):
|
|
try:
|
|
# Early check to avoid initializing GeoIP2 on invalid addresses
|
|
if not ip:
|
|
raise forms.ValidationError('No IP')
|
|
validate_ipv46_address(ip)
|
|
geoip = GeoIP2()
|
|
value = geoip.country_code(ip)
|
|
# Annoyingly, we have to catch both django's GeoIP2Exception (setup
|
|
# issue) and geoip2's GeoIP2Error (lookup issue)
|
|
except (forms.ValidationError, GeoIP2Exception, GeoIP2Error):
|
|
value = ''
|
|
return value
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return self.extension.name if self.extension else self.user
|
|
|
|
def __str__(self) -> str:
|
|
return f'Abuse Report for {self.type} {self.name}'
|
|
|
|
@classmethod
|
|
def exists(cls, user_id: int, extension_id: int, rating_id: int = None) -> bool:
|
|
if rating_id is None:
|
|
return cls.objects.filter(
|
|
reporter_id=user_id, extension_id=extension_id, type=ABUSE_TYPE_EXTENSION
|
|
).exists()
|
|
return cls.objects.filter(
|
|
reporter_id=user_id,
|
|
extension_id=extension_id,
|
|
rating_id=rating_id,
|
|
type=ABUSE_TYPE_RATING,
|
|
).exists()
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('abuse:view-report', args=[self.pk])
|
|
|
|
def get_admin_url(self):
|
|
return reverse('admin:abuse_abusereport_change', args=[self.pk])
|