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:
parent
e507dd5814
commit
ba2f47edb7
16
.pre-commit-config.yaml
Normal file
16
.pre-commit-config.yaml
Normal 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'
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user