Add Black config and pre-commit hook (#231)

* Add black config to pyproject.toml

* Add flake8 config

* Add pre-commit black hook
This commit is contained in:
havk 2022-09-29 01:16:40 +02:00 committed by GitHub
parent e507dd5814
commit ba2f47edb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 124 additions and 71 deletions

2
.flake8 Normal file
View File

@ -0,0 +1,2 @@
[flake8]
max-line-length = 88

16
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,16 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
#- repo: https://github.com/pre-commit/pre-commit-hooks
#rev: v3.2.0
#hooks:
#- id: trailing-whitespace
#- id: end-of-file-fixer
#- id: check-yaml
#- id: check-added-large-files
- repo: https://github.com/psf/black
rev: 22.8.0
hooks:
- id: black
exclude: 'migrations|^shynet/shynet/settings.py'

View File

@ -39,3 +39,6 @@ mypy = "^0.910"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.black]
line-length = 88

View File

@ -15,27 +15,26 @@ def _default_uuid():
class Session(models.Model):
uuid = models.UUIDField(default=_default_uuid, primary_key=True)
service = models.ForeignKey(
Service, verbose_name=_('Service'),
on_delete=models.CASCADE, db_index=True
Service, verbose_name=_("Service"), on_delete=models.CASCADE, db_index=True
)
# Cross-session identification; optional, and provided by the service
identifier = models.TextField(
blank=True, db_index=True, verbose_name=_('Identifier')
blank=True, db_index=True, verbose_name=_("Identifier")
)
# Time
start_time = models.DateTimeField(
default=timezone.now, db_index=True, verbose_name=_('Start time')
default=timezone.now, db_index=True, verbose_name=_("Start time")
)
last_seen = models.DateTimeField(
default=timezone.now, db_index=True, verbose_name=_('Last seen')
default=timezone.now, db_index=True, verbose_name=_("Last seen")
)
# Core request information
user_agent = models.TextField(verbose_name=_('User agent'))
browser = models.TextField(verbose_name=_('Browser'))
device = models.TextField(verbose_name=_('Device'))
user_agent = models.TextField(verbose_name=_("User agent"))
browser = models.TextField(verbose_name=_("Browser"))
device = models.TextField(verbose_name=_("Device"))
device_type = models.CharField(
max_length=7,
choices=[
@ -46,23 +45,25 @@ class Session(models.Model):
("OTHER", _("Other")),
],
default="OTHER",
verbose_name=_('Device type')
verbose_name=_("Device type"),
)
os = models.TextField(verbose_name=_('OS'))
ip = models.GenericIPAddressField(db_index=True, null=True, verbose_name=_('IP'))
os = models.TextField(verbose_name=_("OS"))
ip = models.GenericIPAddressField(db_index=True, null=True, verbose_name=_("IP"))
# GeoIP data
asn = models.TextField(blank=True, verbose_name=_('Asn'))
country = models.TextField(blank=True, verbose_name=_('Country'))
longitude = models.FloatField(null=True, verbose_name=_('Longitude'))
latitude = models.FloatField(null=True, verbose_name=_('Latitude'))
time_zone = models.TextField(blank=True, verbose_name=_('Time zone'))
asn = models.TextField(blank=True, verbose_name=_("Asn"))
country = models.TextField(blank=True, verbose_name=_("Country"))
longitude = models.FloatField(null=True, verbose_name=_("Longitude"))
latitude = models.FloatField(null=True, verbose_name=_("Latitude"))
time_zone = models.TextField(blank=True, verbose_name=_("Time zone"))
is_bounce = models.BooleanField(default=True, db_index=True, verbose_name=_('Is bounce'))
is_bounce = models.BooleanField(
default=True, db_index=True, verbose_name=_("Is bounce")
)
class Meta:
verbose_name = _('Session')
verbose_name_plural = _('Sessions')
verbose_name = _("Session")
verbose_name_plural = _("Sessions")
ordering = ["-start_time"]
indexes = [
models.Index(fields=["service", "-start_time"]),
@ -96,8 +97,7 @@ class Session(models.Model):
class Hit(models.Model):
session = models.ForeignKey(
Session, on_delete=models.CASCADE, db_index=True,
verbose_name=_('Session')
Session, on_delete=models.CASCADE, db_index=True, verbose_name=_("Session")
)
initial = models.BooleanField(default=True, db_index=True)
@ -119,8 +119,8 @@ class Hit(models.Model):
service = models.ForeignKey(Service, on_delete=models.CASCADE, db_index=True)
class Meta:
verbose_name = _('Hit')
verbose_name_plural = _('Hits')
verbose_name = _("Hit")
verbose_name_plural = _("Hits")
ordering = ["-start_time"]
indexes = [
models.Index(fields=["session", "-start_time"]),
@ -129,7 +129,6 @@ class Hit(models.Model):
models.Index(fields=["session", "referrer"]),
]
@property
def duration(self):
return self.last_seen - self.start_time

View File

@ -2,5 +2,5 @@ from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'api'
default_auto_field = "django.db.models.BigAutoField"
name = "api"

View File

@ -6,11 +6,11 @@ from core.models import User
class ApiTokenRequiredMixin:
def _get_user_by_token(self, request):
token = request.headers.get('Authorization')
if not token or not token.startswith('Token '):
token = request.headers.get("Authorization")
if not token or not token.startswith("Token "):
return AnonymousUser()
token = token.split(' ')[1]
token = token.split(" ")[1]
user = User.objects.filter(api_token=token).first()
return user if user else AnonymousUser()

View File

@ -24,7 +24,7 @@ class DashboardApiView(ApiTokenRequiredMixin, DateRangeMixin, View):
Q(owner=request.user) | Q(collaborators__in=[request.user])
).distinct()
uuid = request.GET.get('uuid')
uuid = request.GET.get("uuid")
if uuid and is_valid_uuid(uuid):
services = services.filter(uuid=uuid)
@ -32,29 +32,29 @@ class DashboardApiView(ApiTokenRequiredMixin, DateRangeMixin, View):
start = self.get_start_date()
end = self.get_end_date()
except ValueError:
return JsonResponse(status=400, data={'error': 'Invalid date format'})
return JsonResponse(status=400, data={"error": "Invalid date format"})
services_data = [
{
'name': s.name,
'uuid': s.uuid,
'link': s.link,
'stats': s.get_core_stats(start, end),
"name": s.name,
"uuid": s.uuid,
"link": s.link,
"stats": s.get_core_stats(start, end),
}
for s in services
]
services_data = self._convert_querysets_to_lists(services_data)
return JsonResponse(data={'services': services_data})
return JsonResponse(data={"services": services_data})
def _convert_querysets_to_lists(self, services_data):
for service_data in services_data:
for key, value in service_data['stats'].items():
for key, value in service_data["stats"].items():
if isinstance(value, QuerySet):
service_data['stats'][key] = list(value)
for key, value in service_data['stats']['compare'].items():
service_data["stats"][key] = list(value)
for key, value in service_data["stats"]["compare"].items():
if isinstance(value, QuerySet):
service_data['stats']['compare'][key] = list(value)
service_data["stats"]["compare"][key] = list(value)
return services_data

View File

@ -64,38 +64,51 @@ class Service(models.Model):
SERVICE_STATUSES = [(ACTIVE, _("Active")), (ARCHIVED, _("Archived"))]
uuid = models.UUIDField(default=_default_uuid, primary_key=True)
name = models.TextField(max_length=64, verbose_name=_('Name'))
name = models.TextField(max_length=64, verbose_name=_("Name"))
owner = models.ForeignKey(
User, verbose_name=_('Owner'),
on_delete=models.CASCADE, related_name="owning_services"
User,
verbose_name=_("Owner"),
on_delete=models.CASCADE,
related_name="owning_services",
)
collaborators = models.ManyToManyField(
User, verbose_name=_('Collaborators'),
related_name="collaborating_services", blank=True
User,
verbose_name=_("Collaborators"),
related_name="collaborating_services",
blank=True,
)
created = models.DateTimeField(auto_now_add=True, verbose_name=_('created'))
link = models.URLField(blank=True, verbose_name=_('link'))
origins = models.TextField(default="*", verbose_name=_('origins'))
created = models.DateTimeField(auto_now_add=True, verbose_name=_("created"))
link = models.URLField(blank=True, verbose_name=_("link"))
origins = models.TextField(default="*", verbose_name=_("origins"))
status = models.CharField(
max_length=2, choices=SERVICE_STATUSES, default=ACTIVE, db_index=True,
verbose_name=_('status')
max_length=2,
choices=SERVICE_STATUSES,
default=ACTIVE,
db_index=True,
verbose_name=_("status"),
)
respect_dnt = models.BooleanField(default=True, verbose_name=_('Respect dnt'))
ignore_robots = models.BooleanField(default=False, verbose_name=_('Ignore robots'))
collect_ips = models.BooleanField(default=True, verbose_name=_('Collect ips'))
respect_dnt = models.BooleanField(default=True, verbose_name=_("Respect dnt"))
ignore_robots = models.BooleanField(default=False, verbose_name=_("Ignore robots"))
collect_ips = models.BooleanField(default=True, verbose_name=_("Collect ips"))
ignored_ips = models.TextField(
default="", blank=True, validators=[_validate_network_list],
verbose_name=_('Igored ips')
default="",
blank=True,
validators=[_validate_network_list],
verbose_name=_("Igored ips"),
)
hide_referrer_regex = models.TextField(
default="", blank=True, validators=[_validate_regex],
verbose_name=_('Hide referrer regex')
default="",
blank=True,
validators=[_validate_regex],
verbose_name=_("Hide referrer regex"),
)
script_inject = models.TextField(
default="", blank=True, verbose_name=_("Script inject")
)
script_inject = models.TextField(default="", blank=True, verbose_name=_('Script inject'))
class Meta:
verbose_name = _('Service')
verbose_name_plural = _('Services')
verbose_name = _("Service")
verbose_name_plural = _("Services")
ordering = ["name", "uuid"]
def __str__(self):

View File

@ -25,9 +25,15 @@ class ServiceForm(forms.ModelForm):
"name": forms.TextInput(),
"origins": forms.TextInput(),
"ignored_ips": forms.TextInput(),
"respect_dnt": forms.RadioSelect(choices=[(True, _("Yes")), (False, _("No"))]),
"collect_ips": forms.RadioSelect(choices=[(True, _("Yes")), (False, _("No"))]),
"ignore_robots": forms.RadioSelect(choices=[(True, _("Yes")), (False, _("No"))]),
"respect_dnt": forms.RadioSelect(
choices=[(True, _("Yes")), (False, _("No"))]
),
"collect_ips": forms.RadioSelect(
choices=[(True, _("Yes")), (False, _("No"))]
),
"ignore_robots": forms.RadioSelect(
choices=[(True, _("Yes")), (False, _("No"))]
),
"hide_referrer_regex": forms.TextInput(),
"script_inject": forms.Textarea(attrs={"class": "font-mono", "rows": 5}),
}
@ -45,17 +51,29 @@ class ServiceForm(forms.ModelForm):
"origins": _(
"At what origins does the service operate? Use commas to separate multiple values. This sets CORS headers, so use '*' if you're not sure (or don't care)."
),
"respect_dnt": _("Should visitors who have enabled <a href='https://en.wikipedia.org/wiki/Do_Not_Track'>Do Not Track</a> be excluded from all data?"),
"ignored_ips": _("A comma-separated list of IP addresses or IP ranges (IPv4 and IPv6) to exclude from tracking (e.g., '192.168.0.2, 127.0.0.1/32')."),
"ignore_robots": _("Should sessions generated by bots be excluded from tracking?"),
"hide_referrer_regex": _("Any referrers that match this <a href='https://regexr.com/'>RegEx</a> will not be listed in the referrer summary. Sessions will still be tracked normally. No effect if left blank."),
"script_inject": _("Optional additional JavaScript to inject at the end of the Shynet script. This code will be injected on every page where this service is installed."),
"respect_dnt": _(
"Should visitors who have enabled <a href='https://en.wikipedia.org/wiki/Do_Not_Track'>Do Not Track</a> be excluded from all data?"
),
"ignored_ips": _(
"A comma-separated list of IP addresses or IP ranges (IPv4 and IPv6) to exclude from tracking (e.g., '192.168.0.2, 127.0.0.1/32')."
),
"ignore_robots": _(
"Should sessions generated by bots be excluded from tracking?"
),
"hide_referrer_regex": _(
"Any referrers that match this <a href='https://regexr.com/'>RegEx</a> will not be listed in the referrer summary. Sessions will still be tracked normally. No effect if left blank."
),
"script_inject": _(
"Optional additional JavaScript to inject at the end of the Shynet script. This code will be injected on every page where this service is installed."
),
}
collect_ips = forms.BooleanField(
help_text=_("IP address collection is disabled globally by your administrator.")
if settings.BLOCK_ALL_IPS
else _("Should individual IP addresses be collected? IP metadata (location, host, etc) will still be collected."),
else _(
"Should individual IP addresses be collected? IP metadata (location, host, etc) will still be collected."
),
widget=forms.RadioSelect(choices=[(True, _("Yes")), (False, _("No"))]),
initial=False if settings.BLOCK_ALL_IPS else True,
required=False,
@ -68,7 +86,9 @@ class ServiceForm(forms.ModelForm):
return False if settings.BLOCK_ALL_IPS else collect_ips
collaborators = forms.CharField(
help_text=_("Which users on this Shynet instance should have read-only access to this service? (Comma separated list of emails.)"),
help_text=_(
"Which users on this Shynet instance should have read-only access to this service? (Comma separated list of emails.)"
),
required=False,
)

View File

@ -161,4 +161,4 @@ class RefreshApiTokenView(LoginRequiredMixin, View):
def post(self, request):
request.user.api_token = _default_api_token()
request.user.save()
return redirect('account_change_password')
return redirect("account_change_password")