Browse Source

chore(api): update code style (mostly PEP 8), no functional changes

fixes too broad exception clauses
renames unused method variables to _, *_
uses "not in" instead of "not .. in .."
fixes number of blank lines
fixes line length
fixes import order
fixes spaces in func arguments
changes doc strings to use """
changes function names, variable names to lowercase with underscores
removes redundant paranthesis
fixes indentation
adds hints for pycharm type checker
fixes shadowed names
marks some only privately used methods as private
Nils Wisiol 6 years ago
parent
commit
9219915fa7

+ 1 - 0
api/api/settings_quick_test.py

@@ -1,5 +1,6 @@
 from api.settings import *
 from api.settings import *
 
 
+# noinspection PyUnresolvedReferences
 DATABASES = {
 DATABASES = {
     'default': {
     'default': {
         'ENGINE': 'django.db.backends.sqlite3',
         'ENGINE': 'django.db.backends.sqlite3',

+ 4 - 4
api/desecapi/authentication.py

@@ -48,7 +48,7 @@ class BasicTokenAuthentication(BaseAuthentication):
         try:
         try:
             user, key = base64.b64decode(basic).decode(HTTP_HEADER_ENCODING).split(':')
             user, key = base64.b64decode(basic).decode(HTTP_HEADER_ENCODING).split(':')
             token = self.model.objects.get(key=key)
             token = self.model.objects.get(key=key)
-        except:
+        except Exception:
             raise exceptions.AuthenticationFailed(invalid_token_message)
             raise exceptions.AuthenticationFailed(invalid_token_message)
 
 
         if not token.user.is_active:
         if not token.user.is_active:
@@ -72,16 +72,16 @@ class URLParamAuthentication(BaseAuthentication):
         using URL parameters.  Otherwise returns `None`.
         using URL parameters.  Otherwise returns `None`.
         """
         """
 
 
-        if not 'username' in request.query_params:
+        if 'username' not in request.query_params:
             msg = 'No username URL parameter provided.'
             msg = 'No username URL parameter provided.'
             raise exceptions.AuthenticationFailed(msg)
             raise exceptions.AuthenticationFailed(msg)
-        if not 'password' in request.query_params:
+        if 'password' not in request.query_params:
             msg = 'No password URL parameter provided.'
             msg = 'No password URL parameter provided.'
             raise exceptions.AuthenticationFailed(msg)
             raise exceptions.AuthenticationFailed(msg)
 
 
         return self.authenticate_credentials(request.query_params['username'], request.query_params['password'])
         return self.authenticate_credentials(request.query_params['username'], request.query_params['password'])
 
 
-    def authenticate_credentials(self, userid, key):
+    def authenticate_credentials(self, _, key):
         try:
         try:
             token = self.model.objects.get(key=key)
             token = self.model.objects.get(key=key)
         except self.model.DoesNotExist:
         except self.model.DoesNotExist:

+ 2 - 0
api/desecapi/emails.py

@@ -2,6 +2,7 @@ from django.template.loader import get_template
 from django.core.mail import EmailMessage
 from django.core.mail import EmailMessage
 from rest_framework.reverse import reverse
 from rest_framework.reverse import reverse
 
 
+
 def send_account_lock_email(request, user):
 def send_account_lock_email(request, user):
     content_tmpl = get_template('emails/captcha/content.txt')
     content_tmpl = get_template('emails/captcha/content.txt')
     subject_tmpl = get_template('emails/captcha/subject.txt')
     subject_tmpl = get_template('emails/captcha/subject.txt')
@@ -16,6 +17,7 @@ def send_account_lock_email(request, user):
                          [user.email])
                          [user.email])
     email.send()
     email.send()
 
 
+
 def send_token_email(context, user):
 def send_token_email(context, user):
     content_tmpl = get_template('emails/user-token/content.txt')
     content_tmpl = get_template('emails/user-token/content.txt')
     subject_tmpl = get_template('emails/user-token/subject.txt')
     subject_tmpl = get_template('emails/user-token/subject.txt')

+ 4 - 2
api/desecapi/exceptions.py

@@ -1,5 +1,7 @@
-from rest_framework.exceptions import APIException
 import json
 import json
+from json import JSONDecodeError
+
+from rest_framework.exceptions import APIException
 
 
 
 
 class PdnsException(APIException):
 class PdnsException(APIException):
@@ -11,5 +13,5 @@ class PdnsException(APIException):
         else:
         else:
             try:
             try:
                 self.detail = json.loads(response.text)['error']
                 self.detail = json.loads(response.text)['error']
-            except:
+            except (JSONDecodeError, KeyError):
                 self.detail = response.text
                 self.detail = response.text

+ 5 - 3
api/desecapi/management/commands/privacy-chores.py

@@ -1,8 +1,10 @@
+from datetime import timedelta
+
 from django.core.management import BaseCommand
 from django.core.management import BaseCommand
-from desecapi.models import User
-from api import settings
 from django.utils import timezone
 from django.utils import timezone
-from datetime import timedelta
+
+from api import settings
+from desecapi.models import User
 
 
 
 
 class Command(BaseCommand):
 class Command(BaseCommand):

+ 3 - 1
api/desecapi/management/commands/sync-from-pdns.py

@@ -1,4 +1,5 @@
 from django.core.management import BaseCommand, CommandError
 from django.core.management import BaseCommand, CommandError
+
 from desecapi.models import Domain
 from desecapi.models import Domain
 
 
 
 
@@ -6,7 +7,8 @@ class Command(BaseCommand):
     help = 'Import authoritative data from pdns, making the local database consistent with pdns.'
     help = 'Import authoritative data from pdns, making the local database consistent with pdns.'
 
 
     def add_arguments(self, parser):
     def add_arguments(self, parser):
-        parser.add_argument('domain-name', nargs='*', help='Domain name to import. If omitted, will import all domains that are known locally.')
+        parser.add_argument('domain-name', nargs='*',
+                            help='Domain name to import. If omitted, will import all domains that are known locally.')
 
 
     def handle(self, *args, **options):
     def handle(self, *args, **options):
         domains = Domain.objects.all()
         domains = Domain.objects.all()

+ 28 - 25
api/desecapi/models.py

@@ -1,16 +1,20 @@
+import datetime
+import random
+import time
+import uuid
+from base64 import b64encode
+from collections import OrderedDict
+from os import urandom
+
+import rest_framework.authtoken.models
 from django.conf import settings
 from django.conf import settings
-from django.db import models, transaction
 from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
 from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
-from django.utils import timezone
 from django.core.exceptions import SuspiciousOperation, ValidationError
 from django.core.exceptions import SuspiciousOperation, ValidationError
-from desecapi import pdns, mixins
-import datetime, uuid
 from django.core.validators import MinValueValidator, RegexValidator
 from django.core.validators import MinValueValidator, RegexValidator
-from collections import OrderedDict
-import rest_framework.authtoken.models
-import time, random
-from os import urandom
-from base64 import b64encode
+from django.db import models, transaction
+from django.utils import timezone
+
+from desecapi import pdns, mixins
 
 
 
 
 def validate_lower(value):
 def validate_lower(value):
@@ -52,9 +56,7 @@ class MyUserManager(BaseUserManager):
         Creates and saves a superuser with the given email, date of
         Creates and saves a superuser with the given email, date of
         birth and password.
         birth and password.
         """
         """
-        user = self.create_user(email,
-                                password=password
-        )
+        user = self.create_user(email, password=password)
         user.is_admin = True
         user.is_admin = True
         user.save(using=self._db)
         user.save(using=self._db)
         return user
         return user
@@ -72,8 +74,8 @@ class Token(rest_framework.authtoken.models.Token):
 
 
     def save(self, *args, **kwargs):
     def save(self, *args, **kwargs):
         if not self.user_specific_id:
         if not self.user_specific_id:
-            self.user_specific_id = random.randrange(16**8)
-        super().save(*args, **kwargs) # Call the "real" save() method.
+            self.user_specific_id = random.randrange(16 ** 8)
+        super().save(*args, **kwargs)  # Call the "real" save() method.
 
 
     def generate_key(self):
     def generate_key(self):
         return b64encode(urandom(21)).decode('utf-8').replace('/', '-').replace('=', '_').replace('+', '.')
         return b64encode(urandom(21)).decode('utf-8').replace('/', '-').replace('=', '_').replace('+', '.')
@@ -92,9 +94,9 @@ class User(AbstractBaseUser):
     is_active = models.BooleanField(default=True)
     is_active = models.BooleanField(default=True)
     is_admin = models.BooleanField(default=False)
     is_admin = models.BooleanField(default=False)
     registration_remote_ip = models.CharField(max_length=1024, blank=True)
     registration_remote_ip = models.CharField(max_length=1024, blank=True)
-    locked = models.DateTimeField(null=True,blank=True)
+    locked = models.DateTimeField(null=True, blank=True)
     created = models.DateTimeField(auto_now_add=True)
     created = models.DateTimeField(auto_now_add=True)
-    limit_domains = models.IntegerField(default=settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT,null=True,blank=True)
+    limit_domains = models.IntegerField(default=settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT, null=True, blank=True)
     dyn = models.BooleanField(default=False)
     dyn = models.BooleanField(default=False)
 
 
     objects = MyUserManager()
     objects = MyUserManager()
@@ -118,19 +120,21 @@ class User(AbstractBaseUser):
     def __str__(self):
     def __str__(self):
         return self.email
         return self.email
 
 
-    def has_perm(self, perm, obj=None):
-        "Does the user have a specific permission?"
+    # noinspection PyMethodMayBeStatic
+    def has_perm(self, *_):
+        """Does the user have a specific permission?"""
         # Simplest possible answer: Yes, always
         # Simplest possible answer: Yes, always
         return True
         return True
 
 
-    def has_module_perms(self, app_label):
-        "Does the user have permissions to view the app `app_label`?"
+    # noinspection PyMethodMayBeStatic
+    def has_module_perms(self, *_):
+        """Does the user have permissions to view the app `app_label`?"""
         # Simplest possible answer: Yes, always
         # Simplest possible answer: Yes, always
         return True
         return True
 
 
     @property
     @property
     def is_staff(self):
     def is_staff(self):
-        "Is the user a member of staff?"
+        """Is the user a member of staff?"""
         # Simplest possible answer: All admins are staff
         # Simplest possible answer: All admins are staff
         return self.is_admin
         return self.is_admin
 
 
@@ -222,7 +226,7 @@ class Domain(models.Model, mixins.SetterMixin):
             except Domain.DoesNotExist:
             except Domain.DoesNotExist:
                 pass
                 pass
             else:
             else:
-                rrsets = RRset.plain_to_RRsets([
+                rrsets = RRset.plain_to_rrsets([
                     {'subname': subname, 'type': 'NS', 'ttl': 3600,
                     {'subname': subname, 'type': 'NS', 'ttl': 3600,
                      'contents': settings.DEFAULT_NS},
                      'contents': settings.DEFAULT_NS},
                     {'subname': subname, 'type': 'DS', 'ttl': 60,
                     {'subname': subname, 'type': 'DS', 'ttl': 60,
@@ -458,9 +462,8 @@ class RRset(models.Model, mixins.SetterMixin):
     DEAD_TYPES = ('ALIAS', 'DNAME')
     DEAD_TYPES = ('ALIAS', 'DNAME')
     RESTRICTED_TYPES = ('SOA', 'RRSIG', 'DNSKEY', 'NSEC3PARAM', 'OPT')
     RESTRICTED_TYPES = ('SOA', 'RRSIG', 'DNSKEY', 'NSEC3PARAM', 'OPT')
 
 
-
     class Meta:
     class Meta:
-        unique_together = (("domain","subname","type"),)
+        unique_together = (("domain", "subname", "type"),)
 
 
     def __init__(self, *args, **kwargs):
     def __init__(self, *args, **kwargs):
         self._dirties = set()
         self._dirties = set()
@@ -526,7 +529,7 @@ class RRset(models.Model, mixins.SetterMixin):
             self._dirties = {}
             self._dirties = {}
 
 
     @staticmethod
     @staticmethod
-    def plain_to_RRsets(datas, *, domain):
+    def plain_to_rrsets(datas, *, domain):
         rrsets = {}
         rrsets = {}
         for data in datas:
         for data in datas:
             rrset = RRset(domain=domain, subname=data['subname'],
             rrset = RRset(domain=domain, subname=data['subname'],

+ 17 - 10
api/desecapi/pdns.py

@@ -1,10 +1,11 @@
-import requests
 import json
 import json
 import random
 import random
+
+import requests
+
 from api import settings
 from api import settings
 from desecapi.exceptions import PdnsException
 from desecapi.exceptions import PdnsException
 
 
-
 headers_nslord = {
 headers_nslord = {
     'Accept': 'application/json',
     'Accept': 'application/json',
     'User-Agent': 'desecapi',
     'User-Agent': 'desecapi',
@@ -40,7 +41,7 @@ def _pdns_delete_zone(domain):
         else:
         else:
             raise PdnsException(r2)
             raise PdnsException(r2)
 
 
-    return (r1, r2)
+    return r1, r2
 
 
 
 
 def _pdns_request(method, path, body=None, acceptable_range=range(200, 300)):
 def _pdns_request(method, path, body=None, acceptable_range=range(200, 300)):
@@ -126,13 +127,19 @@ def get_rrset_datas(domain):
 
 
 
 
 def set_rrsets(domain, rrsets, notify=True):
 def set_rrsets(domain, rrsets, notify=True):
-    data = {'rrsets':
-        [{'name': rrset.name, 'type': rrset.type, 'ttl': rrset.ttl,
-          'changetype': 'REPLACE',
-          'records': [{'content': record.content, 'disabled': False}
-                      for record in rrset.records.all()]
-          }
-         for rrset in rrsets]
+    data = {
+        'rrsets':
+        [
+            {
+                'name': rrset.name, 'type': rrset.type, 'ttl': rrset.ttl,
+                'changetype': 'REPLACE',
+                'records': [
+                    {'content': record.content, 'disabled': False}
+                    for record in rrset.records.all()
+                ]
+            }
+            for rrset in rrsets
+        ]
     }
     }
     _pdns_patch('/zones/' + domain.pdns_id, data)
     _pdns_patch('/zones/' + domain.pdns_id, data)
 
 

+ 23 - 15
api/desecapi/serializers.py

@@ -1,14 +1,16 @@
+import re
+
+import django.core.exceptions
 from django.core.validators import RegexValidator
 from django.core.validators import RegexValidator
+from django.db import models, transaction
+from djoser import serializers as djoser_serializers
 from rest_framework import serializers
 from rest_framework import serializers
 from rest_framework.exceptions import ValidationError
 from rest_framework.exceptions import ValidationError
-from desecapi.models import Domain, Donation, User, RR, RRset, Token
-from djoser import serializers as djoserSerializers
-from django.db import models, transaction
-import django.core.exceptions
-from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
-import re
 from rest_framework.fields import empty
 from rest_framework.fields import empty
 from rest_framework.settings import api_settings
 from rest_framework.settings import api_settings
+from rest_framework_bulk import BulkListSerializer, BulkSerializerMixin
+
+from desecapi.models import Domain, Donation, User, RR, RRset, Token
 
 
 
 
 class TokenSerializer(serializers.ModelSerializer):
 class TokenSerializer(serializers.ModelSerializer):
@@ -44,10 +46,12 @@ class RRsetBulkListSerializer(BulkListSerializer):
         rrsets = {(obj.subname, obj.type): obj for obj in queryset.filter(q)}
         rrsets = {(obj.subname, obj.type): obj for obj in queryset.filter(q)}
         instance = [rrsets.get((data.get('subname', ''), data['type']), None)
         instance = [rrsets.get((data.get('subname', ''), data['type']), None)
                     for data in validated_data]
                     for data in validated_data]
+        # noinspection PyUnresolvedReferences,PyProtectedMember
         return self.child._save(instance, validated_data)
         return self.child._save(instance, validated_data)
 
 
     @transaction.atomic
     @transaction.atomic
     def create(self, validated_data):
     def create(self, validated_data):
+        # noinspection PyUnresolvedReferences,PyProtectedMember
         return self.child._save([None] * len(validated_data), validated_data)
         return self.child._save([None] * len(validated_data), validated_data)
 
 
 
 
@@ -122,7 +126,7 @@ class RRsetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
                 rrset.ttl = data.get('ttl', rrset.ttl)
                 rrset.ttl = data.get('ttl', rrset.ttl)
             else:
             else:
                 # No known instance (creation or meaningless request)
                 # No known instance (creation or meaningless request)
-                if not 'ttl' in data:
+                if 'ttl' not in data:
                     if records:
                     if records:
                         # If we have records, this is a creation request, so we
                         # If we have records, this is a creation request, so we
                         # need a TTL.
                         # need a TTL.
@@ -202,7 +206,8 @@ class RRsetSerializer(BulkSerializerMixin, serializers.ModelSerializer):
     def create(self, validated_data):
     def create(self, validated_data):
         return self._save(None, validated_data)
         return self._save(None, validated_data)
 
 
-    def validate_type(self, value):
+    @staticmethod
+    def validate_type(value):
         if value in RRset.DEAD_TYPES:
         if value in RRset.DEAD_TYPES:
             raise serializers.ValidationError(
             raise serializers.ValidationError(
                 "The %s RRset type is currently unsupported." % value)
                 "The %s RRset type is currently unsupported." % value)
@@ -234,17 +239,19 @@ class DonationSerializer(serializers.ModelSerializer):
         model = Donation
         model = Donation
         fields = ('name', 'iban', 'bic', 'amount', 'message', 'email')
         fields = ('name', 'iban', 'bic', 'amount', 'message', 'email')
 
 
-    def validate_bic(self, value):
+    @staticmethod
+    def validate_bic(value):
         return re.sub(r'[\s]', '', value)
         return re.sub(r'[\s]', '', value)
 
 
-    def validate_iban(self, value):
+    @staticmethod
+    def validate_iban(value):
         return re.sub(r'[\s]', '', value)
         return re.sub(r'[\s]', '', value)
 
 
 
 
-class UserSerializer(djoserSerializers.UserSerializer):
+class UserSerializer(djoser_serializers.UserSerializer):
     locked = serializers.SerializerMethodField()
     locked = serializers.SerializerMethodField()
 
 
-    class Meta(djoserSerializers.UserSerializer.Meta):
+    class Meta(djoser_serializers.UserSerializer.Meta):
         fields = tuple(User.REQUIRED_FIELDS) + (
         fields = tuple(User.REQUIRED_FIELDS) + (
             User.USERNAME_FIELD,
             User.USERNAME_FIELD,
             'dyn',
             'dyn',
@@ -253,13 +260,14 @@ class UserSerializer(djoserSerializers.UserSerializer):
         )
         )
         read_only_fields = ('dyn', 'limit_domains', 'locked',)
         read_only_fields = ('dyn', 'limit_domains', 'locked',)
 
 
-    def get_locked(self, obj):
+    @staticmethod
+    def get_locked(obj):
         return bool(obj.locked)
         return bool(obj.locked)
 
 
 
 
-class UserCreateSerializer(djoserSerializers.UserCreateSerializer):
+class UserCreateSerializer(djoser_serializers.UserCreateSerializer):
 
 
-    class Meta(djoserSerializers.UserCreateSerializer.Meta):
+    class Meta(djoser_serializers.UserCreateSerializer.Meta):
         fields = tuple(User.REQUIRED_FIELDS) + (
         fields = tuple(User.REQUIRED_FIELDS) + (
             User.USERNAME_FIELD,
             User.USERNAME_FIELD,
             'password',
             'password',

+ 6 - 3
api/desecapi/templatetags/sepa_extras.py

@@ -1,14 +1,17 @@
-from django import template
-import unicodedata
 import re
 import re
+import unicodedata
+
+from django import template
 
 
 register = template.Library()
 register = template.Library()
 
 
+
 def clean(value):
 def clean(value):
     """Replaces non-ascii characters with their closest ascii
     """Replaces non-ascii characters with their closest ascii
        representation and then removes everything but [A-Za-z0-9 ]"""
        representation and then removes everything but [A-Za-z0-9 ]"""
     normalized = unicodedata.normalize('NFKD', value)
     normalized = unicodedata.normalize('NFKD', value)
-    cleaned = re.sub(r'[^A-Za-z0-9 ]','',normalized)
+    cleaned = re.sub(r'[^A-Za-z0-9 ]', '', normalized)
     return cleaned
     return cleaned
 
 
+
 register.filter('clean', clean)
 register.filter('clean', clean)

+ 7 - 1
api/desecapi/tests/base.py

@@ -301,7 +301,6 @@ class MockPDNSTestCase(APITestCase):
         request.pop('status')
         request.pop('status')
         return request
         return request
 
 
-
     @classmethod
     @classmethod
     def request_pdns_zone_retrieve(cls, name=None):
     def request_pdns_zone_retrieve(cls, name=None):
         return {
         return {
@@ -646,6 +645,13 @@ class DomainOwnerTestCase(DesecTestCase):
     NUM_OWNED_DOMAINS = 2
     NUM_OWNED_DOMAINS = 2
     NUM_OTHER_DOMAINS = 20
     NUM_OTHER_DOMAINS = 20
 
 
+    owner = None
+    my_domains = None
+    other_domains = None
+    my_domain = None
+    other_domain = None
+    token = None
+
     @classmethod
     @classmethod
     def setUpTestDataWithPdns(cls):
     def setUpTestDataWithPdns(cls):
         super().setUpTestDataWithPdns()
         super().setUpTestDataWithPdns()

+ 3 - 3
api/desecapi/tests/testdomains.py

@@ -1,14 +1,14 @@
-import random
 import json
 import json
+import random
 
 
-from django.core import mail
 from django.conf import settings
 from django.conf import settings
+from django.core import mail
 from django.core.exceptions import ValidationError
 from django.core.exceptions import ValidationError
 from rest_framework import status
 from rest_framework import status
 
 
 from desecapi.exceptions import PdnsException
 from desecapi.exceptions import PdnsException
-from desecapi.tests.base import DesecTestCase, DomainOwnerTestCase, LockedDomainOwnerTestCase
 from desecapi.models import Domain
 from desecapi.models import Domain
+from desecapi.tests.base import DesecTestCase, DomainOwnerTestCase, LockedDomainOwnerTestCase
 
 
 
 
 class UnauthenticatedDomainTests(DesecTestCase):
 class UnauthenticatedDomainTests(DesecTestCase):

+ 2 - 2
api/desecapi/tests/testdonations.py

@@ -1,6 +1,6 @@
-from rest_framework.reverse import reverse
-from rest_framework import status
 from django.core import mail
 from django.core import mail
+from rest_framework import status
+from rest_framework.reverse import reverse
 
 
 from desecapi.tests.base import DesecTestCase
 from desecapi.tests.base import DesecTestCase
 
 

+ 1 - 1
api/desecapi/tests/testprivacychores.py

@@ -3,9 +3,9 @@ from datetime import timedelta
 from django.core.management import call_command
 from django.core.management import call_command
 from django.utils import timezone
 from django.utils import timezone
 
 
+from api import settings
 from desecapi.models import User
 from desecapi.models import User
 from desecapi.tests.base import DesecTestCase
 from desecapi.tests.base import DesecTestCase
-from api import settings
 
 
 
 
 class PrivacyChoresCommandTest(DesecTestCase):
 class PrivacyChoresCommandTest(DesecTestCase):

+ 9 - 9
api/desecapi/tests/testregistration.py

@@ -1,24 +1,24 @@
 from datetime import timedelta
 from datetime import timedelta
 
 
+from django.core import mail
 from django.test import RequestFactory
 from django.test import RequestFactory
 from django.utils import timezone
 from django.utils import timezone
-from django.core import mail
 from rest_framework.reverse import reverse
 from rest_framework.reverse import reverse
 from rest_framework.versioning import NamespaceVersioning
 from rest_framework.versioning import NamespaceVersioning
 
 
-from desecapi.tests.base import DesecTestCase
+from api import settings
 from desecapi import models
 from desecapi import models
 from desecapi.emails import send_account_lock_email
 from desecapi.emails import send_account_lock_email
-from api import settings
+from desecapi.tests.base import DesecTestCase
 
 
 
 
 class RegistrationTestCase(DesecTestCase):
 class RegistrationTestCase(DesecTestCase):
 
 
-    def assertRegistration(self, REMOTE_ADDR='', status=201, **kwargs):
+    def assertRegistration(self, remote_addr='', status=201, **kwargs):
         url = reverse('v1:register')
         url = reverse('v1:register')
         post_kwargs = {}
         post_kwargs = {}
-        if REMOTE_ADDR:
-            post_kwargs['REMOTE_ADDR'] = REMOTE_ADDR
+        if remote_addr:
+            post_kwargs['REMOTE_ADDR'] = remote_addr
         response = self.client.post(url, kwargs, **post_kwargs)
         response = self.client.post(url, kwargs, **post_kwargs)
         self.assertStatus(response, status)
         self.assertStatus(response, status)
         return response
         return response
@@ -32,7 +32,7 @@ class SingleRegistrationTestCase(RegistrationTestCase):
         self.assertRegistration(
         self.assertRegistration(
             email=email,
             email=email,
             password=self.random_password(),
             password=self.random_password(),
-            REMOTE_ADDR="1.3.3.7",
+            remote_addr="1.3.3.7",
         )
         )
         self.user = models.User.objects.get(email=email)
         self.user = models.User.objects.get(email=email)
 
 
@@ -59,7 +59,7 @@ class SingleRegistrationTestCase(RegistrationTestCase):
 class MultipleRegistrationTestCase(RegistrationTestCase):
 class MultipleRegistrationTestCase(RegistrationTestCase):
 
 
     def _registrations(self):
     def _registrations(self):
-        pass
+        return []
 
 
     def setUp(self):
     def setUp(self):
         super().setUp()
         super().setUp()
@@ -71,7 +71,7 @@ class MultipleRegistrationTestCase(RegistrationTestCase):
                 email=email,
                 email=email,
                 password=self.random_password(),
                 password=self.random_password(),
                 dyn=True,
                 dyn=True,
-                REMOTE_ADDR=ip,
+                remote_addr=ip,
             )
             )
             user = models.User.objects.get(email=email)
             user = models.User.objects.get(email=email)
             self.assertEqual(user.registration_remote_ip, ip)
             self.assertEqual(user.registration_remote_ip, ip)

+ 1 - 2
api/desecapi/urls/version_1.py

@@ -4,7 +4,6 @@ from rest_framework.routers import SimpleRouter
 
 
 from desecapi import views
 from desecapi import views
 
 
-
 tokens_router = SimpleRouter()
 tokens_router = SimpleRouter()
 tokens_router.register(r'', views.TokenViewSet, base_name='token')
 tokens_router.register(r'', views.TokenViewSet, base_name='token')
 
 
@@ -38,7 +37,7 @@ api_urls = [
     path('domains/<name>/rrsets/', views.RRsetList.as_view(), name='rrsets'),
     path('domains/<name>/rrsets/', views.RRsetList.as_view(), name='rrsets'),
     path('domains/<name>/rrsets/.../<type>/', views.RRsetDetail.as_view(), kwargs={'subname': ''}),
     path('domains/<name>/rrsets/.../<type>/', views.RRsetDetail.as_view(), kwargs={'subname': ''}),
     re_path(r'domains/(?P<name>[^/]+)/rrsets/(?P<subname>[^/]*)\.\.\./(?P<type>[^/]+)/',
     re_path(r'domains/(?P<name>[^/]+)/rrsets/(?P<subname>[^/]*)\.\.\./(?P<type>[^/]+)/',
-                views.RRsetDetail.as_view(), name='rrset'),
+            views.RRsetDetail.as_view(), name='rrset'),
     path('domains/<name>/rrsets/@/<type>/', views.RRsetDetail.as_view(), kwargs={'subname': ''}),
     path('domains/<name>/rrsets/@/<type>/', views.RRsetDetail.as_view(), kwargs={'subname': ''}),
     re_path(r'domains/(?P<name>[^/]+)/rrsets/(?P<subname>[^/]*)@/(?P<type>[^/]+)/',
     re_path(r'domains/(?P<name>[^/]+)/rrsets/(?P<subname>[^/]*)@/(?P<type>[^/]+)/',
             views.RRsetDetail.as_view(), name='rrset@'),
             views.RRsetDetail.as_view(), name='rrset@'),

+ 114 - 93
api/desecapi/views.py

@@ -1,44 +1,45 @@
-from __future__ import unicode_literals
-from django.core.mail import EmailMessage
-from rest_framework.generics import get_object_or_404
+from __future__ import unicode_literals  # TODO remove!
 
 
-from desecapi.models import Domain, User, RRset, Token
-from desecapi.serializers import (
-    DomainSerializer, RRsetSerializer, DonationSerializer, TokenSerializer)
-from rest_framework import generics
-from desecapi.permissions import *
-from rest_framework import permissions
+import base64
+import binascii
+import ipaddress
+import os
+import re
+from datetime import timedelta
+
+import django.core.exceptions
+import djoser.views
+from django.contrib.auth import user_logged_in, user_logged_out
+from django.core.mail import EmailMessage
+from django.db.models import Q
 from django.http import Http404, HttpResponseRedirect
 from django.http import Http404, HttpResponseRedirect
-from rest_framework.views import APIView
-from rest_framework.response import Response
-from rest_framework.reverse import reverse
-from rest_framework.authentication import get_authorization_header
-from desecapi.renderers import PlainTextRenderer
-from dns import resolver
+from django.shortcuts import render
 from django.template.loader import get_template
 from django.template.loader import get_template
-import desecapi.authentication as auth
-import base64, binascii
-from api import settings
-from rest_framework.exceptions import (NotFound, PermissionDenied, ValidationError)
-import django.core.exceptions
-from djoser import views, signals
-from rest_framework import status
-from datetime import timedelta
 from django.utils import timezone
 from django.utils import timezone
-from desecapi.forms import UnlockForm
-from django.shortcuts import render
-from django.db.models import Q
-from desecapi.emails import send_account_lock_email, send_token_email
-import re
-import ipaddress, os
-from rest_framework_bulk import ListBulkCreateUpdateAPIView
-from django.contrib.auth import user_logged_in, user_logged_out
-import djoser.views
+from djoser import views, signals
 from djoser.serializers import TokenSerializer as DjoserTokenSerializer
 from djoser.serializers import TokenSerializer as DjoserTokenSerializer
-from rest_framework.viewsets import GenericViewSet
+from dns import resolver
+from rest_framework import generics
 from rest_framework import mixins
 from rest_framework import mixins
+from rest_framework import status
+from rest_framework.authentication import get_authorization_header
+from rest_framework.exceptions import (NotFound, PermissionDenied, ValidationError)
+from rest_framework.generics import get_object_or_404
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
 from rest_framework.settings import api_settings
 from rest_framework.settings import api_settings
+from rest_framework.views import APIView
+from rest_framework.viewsets import GenericViewSet
+from rest_framework_bulk import ListBulkCreateUpdateAPIView
 
 
+import desecapi.authentication as auth
+from api import settings
+from desecapi.emails import send_account_lock_email, send_token_email
+from desecapi.forms import UnlockForm
+from desecapi.models import Domain, User, RRset, Token
+from desecapi.permissions import *
+from desecapi.renderers import PlainTextRenderer
+from desecapi.serializers import DomainSerializer, RRsetSerializer, DonationSerializer, TokenSerializer
 
 
 patternDyn = re.compile(r'^[A-Za-z-][A-Za-z0-9_-]*\.dedyn\.io$')
 patternDyn = re.compile(r'^[A-Za-z-][A-Za-z0-9_-]*\.dedyn\.io$')
 patternNonDyn = re.compile(r'^([A-Za-z0-9-][A-Za-z0-9_-]*\.)+[A-Za-z]+$')
 patternNonDyn = re.compile(r'^([A-Za-z0-9-][A-Za-z0-9_-]*\.)+[A-Za-z]+$')
@@ -105,7 +106,10 @@ class DomainList(generics.ListCreateAPIView):
     def perform_create(self, serializer):
     def perform_create(self, serializer):
         pattern = patternDyn if self.request.user.dyn else patternNonDyn
         pattern = patternDyn if self.request.user.dyn else patternNonDyn
         if pattern.match(serializer.validated_data['name']) is None:
         if pattern.match(serializer.validated_data['name']) is None:
-            ex = ValidationError(detail={"detail": "This domain name is not well-formed, by policy.", "code": "domain-illformed"})
+            ex = ValidationError(detail={
+                "detail": "This domain name is not well-formed, by policy.",
+                "code": "domain-illformed"}
+            )
             ex.status_code = status.HTTP_409_CONFLICT
             ex.status_code = status.HTTP_409_CONFLICT
             raise ex
             raise ex
 
 
@@ -124,8 +128,12 @@ class DomainList(generics.ListCreateAPIView):
             ex.status_code = status.HTTP_409_CONFLICT
             ex.status_code = status.HTTP_409_CONFLICT
             raise ex
             raise ex
 
 
-        if self.request.user.limit_domains is not None and self.request.user.domains.count() >= self.request.user.limit_domains:
-            ex = ValidationError(detail={"detail": "You reached the maximum number of domains allowed for your account.", "code": "domain-limit"})
+        if (self.request.user.limit_domains is not None and
+                self.request.user.domains.count() >= self.request.user.limit_domains):
+            ex = ValidationError(detail={
+                "detail": "You reached the maximum number of domains allowed for your account.",
+                "code": "domain-limit"
+            })
             ex.status_code = status.HTTP_403_FORBIDDEN
             ex.status_code = status.HTTP_403_FORBIDDEN
             raise ex
             raise ex
 
 
@@ -133,13 +141,16 @@ class DomainList(generics.ListCreateAPIView):
             obj = serializer.save(owner=self.request.user)
             obj = serializer.save(owner=self.request.user)
         except Exception as e:
         except Exception as e:
             if str(e).endswith(' already exists'):
             if str(e).endswith(' already exists'):
-                ex = ValidationError(detail={"detail": "This domain name is unavailable.", "code": "domain-unavailable"})
+                ex = ValidationError(detail={
+                    "detail": "This domain name is unavailable.",
+                    "code": "domain-unavailable"}
+                )
                 ex.status_code = status.HTTP_409_CONFLICT
                 ex.status_code = status.HTTP_409_CONFLICT
                 raise ex
                 raise ex
             else:
             else:
                 raise e
                 raise e
 
 
-        def sendDynDnsEmail(domain):
+        def send_dyn_dns_email(domain):
             content_tmpl = get_template('emails/domain-dyndns/content.txt')
             content_tmpl = get_template('emails/domain-dyndns/content.txt')
             subject_tmpl = get_template('emails/domain-dyndns/subject.txt')
             subject_tmpl = get_template('emails/domain-dyndns/subject.txt')
             from_tmpl = get_template('emails/from.txt')
             from_tmpl = get_template('emails/from.txt')
@@ -156,7 +167,7 @@ class DomainList(generics.ListCreateAPIView):
             email.send()
             email.send()
 
 
         if obj.name.endswith('.dedyn.io'):
         if obj.name.endswith('.dedyn.io'):
-            sendDynDnsEmail(obj)
+            send_dyn_dns_email(obj)
 
 
 
 
 class DomainDetail(generics.RetrieveUpdateDestroyAPIView):
 class DomainDetail(generics.RetrieveUpdateDestroyAPIView):
@@ -220,7 +231,8 @@ class RRsetDetail(generics.RetrieveUpdateDestroyAPIView):
                 api_settings.NON_FIELD_ERRORS_KEY: ['Invalid input, expected a JSON object.']
                 api_settings.NON_FIELD_ERRORS_KEY: ['Invalid input, expected a JSON object.']
             }, code='invalid')
             }, code='invalid')
 
 
-        if request.data.get('records') == []:
+        records = request.data.get('records')
+        if records is not None and len(records) == 0:
             return self.delete(request, *args, **kwargs)
             return self.delete(request, *args, **kwargs)
 
 
         # Attach URL parameters (self.kwargs) to the request body object (request.data),
         # Attach URL parameters (self.kwargs) to the request body object (request.data),
@@ -278,8 +290,10 @@ class RRsetList(ListBulkCreateUpdateAPIView):
         # default='' in the serializer field definition so that during PUT, the
         # default='' in the serializer field definition so that during PUT, the
         # subname value is retained if omitted.
         # subname value is retained if omitted.
         if isinstance(self.request.data, list):
         if isinstance(self.request.data, list):
-            serializer._validated_data = [{**{'subname': ''}, **data}
-                                         for data in serializer.validated_data]
+            serializer._validated_data = [
+                {**{'subname': ''}, **data}
+                for data in serializer.validated_data
+            ]
         else:
         else:
             serializer._validated_data = {**{'subname': ''}, **serializer.validated_data}
             serializer._validated_data = {**{'subname': ''}, **serializer.validated_data}
 
 
@@ -297,7 +311,7 @@ class RRsetList(ListBulkCreateUpdateAPIView):
 
 
 
 
 class Root(APIView):
 class Root(APIView):
-    def get(self, request, format=None):
+    def get(self, request, *_):
         if self.request.user and self.request.user.is_authenticated:
         if self.request.user and self.request.user.is_authenticated:
             return Response({
             return Response({
                 'domains': reverse('domain-list', request=request),
                 'domains': reverse('domain-list', request=request),
@@ -312,19 +326,21 @@ class Root(APIView):
 
 
 
 
 class DnsQuery(APIView):
 class DnsQuery(APIView):
-    def get(self, request, format=None):
-        desecio = resolver.Resolver()
 
 
-        if not 'domain' in request.GET:
+    @staticmethod
+    def get(request, *_):
+        dns_resolver = resolver.Resolver()
+
+        if 'domain' not in request.GET:
             return Response(status=status.HTTP_400_BAD_REQUEST)
             return Response(status=status.HTTP_400_BAD_REQUEST)
 
 
         domain = str(request.GET['domain'])
         domain = str(request.GET['domain'])
 
 
-        def getRecords(domain, type_):
+        def get_records(domain_name, type_):
             records = []
             records = []
             try:
             try:
-                for ip in desecio.query(domain, type_):
-                    records.append(str(ip))
+                for address in dns_resolver.query(domain_name, type_):
+                    records.append(str(address))
             except resolver.NoAnswer:
             except resolver.NoAnswer:
                 return []
                 return []
             except resolver.NoNameservers:
             except resolver.NoNameservers:
@@ -334,24 +350,24 @@ class DnsQuery(APIView):
             return records
             return records
 
 
         # find currently active NS records
         # find currently active NS records
-        nsrecords = getRecords(domain, 'NS')
+        ns_records = get_records(domain, 'NS')
 
 
-        # find desec.io nameserver IP address with standard nameserver
-        ips = desecio.query('ns2.desec.io')
-        desecio.nameservers = []
+        # find desec.io name server IP address with standard name server
+        ips = dns_resolver.query('ns2.desec.io')
+        dns_resolver.nameservers = []
         for ip in ips:
         for ip in ips:
-            desecio.nameservers.append(str(ip))
+            dns_resolver.nameservers.append(str(ip))
 
 
-        # query desec.io nameserver for A and AAAA records
-        arecords = getRecords(domain, 'A')
-        aaaarecords = getRecords(domain, 'AAAA')
+        # query desec.io name server for A and AAAA records
+        a_records = get_records(domain, 'A')
+        aaaa_records = get_records(domain, 'AAAA')
 
 
         return Response({
         return Response({
             'domain': domain,
             'domain': domain,
-            'ns': nsrecords,
-            'a': arecords,
-            'aaaa': aaaarecords,
-            '_nameserver': desecio.nameservers
+            'ns': ns_records,
+            'a': a_records,
+            'aaaa': aaaa_records,
+            '_nameserver': dns_resolver.nameservers
         })
         })
 
 
 
 
@@ -359,25 +375,26 @@ class DynDNS12Update(APIView):
     authentication_classes = (auth.TokenAuthentication, auth.BasicTokenAuthentication, auth.URLParamAuthentication,)
     authentication_classes = (auth.TokenAuthentication, auth.BasicTokenAuthentication, auth.URLParamAuthentication,)
     renderer_classes = [PlainTextRenderer]
     renderer_classes = [PlainTextRenderer]
 
 
-    def findDomain(self, request):
+    def _find_domain(self, request):
         if self.request.user.locked:
         if self.request.user.locked:
             # Error code from https://help.dyn.com/remote-access-api/return-codes/
             # Error code from https://help.dyn.com/remote-access-api/return-codes/
             raise PermissionDenied('abuse')
             raise PermissionDenied('abuse')
 
 
-        def findDomainname(request):
+        def find_domain_name(r):
             # 1. hostname parameter
             # 1. hostname parameter
-            if 'hostname' in request.query_params and request.query_params['hostname'] != 'YES':
-                return request.query_params['hostname']
+            if 'hostname' in r.query_params and r.query_params['hostname'] != 'YES':
+                return r.query_params['hostname']
 
 
             # 2. host_id parameter
             # 2. host_id parameter
-            if 'host_id' in request.query_params:
-                return request.query_params['host_id']
+            if 'host_id' in r.query_params:
+                return r.query_params['host_id']
 
 
             # 3. http basic auth username
             # 3. http basic auth username
             try:
             try:
-                domainname = base64.b64decode(get_authorization_header(request).decode().split(' ')[1].encode()).decode().split(':')[0]
-                if domainname:
-                    return domainname
+                domain_name = base64.b64decode(
+                    get_authorization_header(r).decode().split(' ')[1].encode()).decode().split(':')[0]
+                if domain_name:
+                    return domain_name
             except IndexError:
             except IndexError:
                 pass
                 pass
             except UnicodeDecodeError:
             except UnicodeDecodeError:
@@ -386,31 +403,35 @@ class DynDNS12Update(APIView):
                 pass
                 pass
 
 
             # 4. username parameter
             # 4. username parameter
-            if 'username' in request.query_params:
-                return request.query_params['username']
+            if 'username' in r.query_params:
+                return r.query_params['username']
 
 
             # 5. only domain associated with this user account
             # 5. only domain associated with this user account
-            if len(request.user.domains.all()) == 1:
-                return request.user.domains.all()[0].name
-            if len(request.user.domains.all()) > 1:
-                ex = ValidationError(detail={"detail": "Request does not specify domain unambiguously.", "code": "domain-ambiguous"})
+            if len(r.user.domains.all()) == 1:
+                return r.user.domains.all()[0].name
+            if len(r.user.domains.all()) > 1:
+                ex = ValidationError(detail={
+                    "detail": "Request does not specify domain unambiguously.",
+                    "code": "domain-ambiguous"
+                })
                 ex.status_code = status.HTTP_409_CONFLICT
                 ex.status_code = status.HTTP_409_CONFLICT
                 raise ex
                 raise ex
 
 
             return None
             return None
 
 
-        name = findDomainname(request).lower()
+        name = find_domain_name(request).lower()
 
 
         try:
         try:
             return self.request.user.domains.get(name=name)
             return self.request.user.domains.get(name=name)
         except Domain.DoesNotExist:
         except Domain.DoesNotExist:
             return None
             return None
 
 
-    def findIP(self, request, params, version=4):
+    @staticmethod
+    def find_ip(request, params, version=4):
         if version == 4:
         if version == 4:
-            lookfor = '.'
+            look_for = '.'
         elif version == 6:
         elif version == 6:
-            lookfor = ':'
+            look_for = ':'
         else:
         else:
             raise Exception
             raise Exception
 
 
@@ -419,31 +440,31 @@ class DynDNS12Update(APIView):
             if p in request.query_params:
             if p in request.query_params:
                 if not len(request.query_params[p]):
                 if not len(request.query_params[p]):
                     return None
                     return None
-                if lookfor in request.query_params[p]:
+                if look_for in request.query_params[p]:
                     return request.query_params[p]
                     return request.query_params[p]
 
 
         # Check remote IP address
         # Check remote IP address
         client_ip = get_client_ip(request)
         client_ip = get_client_ip(request)
-        if lookfor in client_ip:
+        if look_for in client_ip:
             return client_ip
             return client_ip
 
 
         # give up
         # give up
         return None
         return None
 
 
-    def findIPv4(self, request):
-        return self.findIP(request, ['myip', 'myipv4', 'ip'])
+    def _find_ip_v4(self, request):
+        return self.find_ip(request, ['myip', 'myipv4', 'ip'])
 
 
-    def findIPv6(self, request):
-        return self.findIP(request, ['myipv6', 'ipv6', 'myip', 'ip'], version=6)
+    def _find_ip_v6(self, request):
+        return self.find_ip(request, ['myipv6', 'ipv6', 'myip', 'ip'], version=6)
 
 
-    def get(self, request, format=None):
-        domain = self.findDomain(request)
+    def get(self, request, *_):
+        domain = self._find_domain(request)
 
 
         if domain is None:
         if domain is None:
             raise NotFound('nohost')
             raise NotFound('nohost')
 
 
-        datas = {'A': self.findIPv4(request), 'AAAA': self.findIPv6(request)}
-        rrsets = RRset.plain_to_RRsets(
+        datas = {'A': self._find_ip_v4(request), 'AAAA': self._find_ip_v6(request)}
+        rrsets = RRset.plain_to_rrsets(
             [{'subname': '', 'type': type_, 'ttl': 60,
             [{'subname': '', 'type': type_, 'ttl': 60,
               'contents': [ip] if ip is not None else []}
               'contents': [ip] if ip is not None else []}
              for type_, ip in datas.items()],
              for type_, ip in datas.items()],
@@ -452,6 +473,7 @@ class DynDNS12Update(APIView):
 
 
         return Response('good', content_type='text/plain')
         return Response('good', content_type='text/plain')
 
 
+
 class DonationList(generics.CreateAPIView):
 class DonationList(generics.CreateAPIView):
     serializer_class = DonationSerializer
     serializer_class = DonationSerializer
 
 
@@ -459,7 +481,7 @@ class DonationList(generics.CreateAPIView):
         iban = serializer.validated_data['iban']
         iban = serializer.validated_data['iban']
         obj = serializer.save()
         obj = serializer.save()
 
 
-        def sendDonationEmails(donation):
+        def send_donation_emails(donation):
             context = {
             context = {
                 'donation': donation,
                 'donation': donation,
                 'creditoridentifier': settings.SEPA['CREDITOR_ID'],
                 'creditoridentifier': settings.SEPA['CREDITOR_ID'],
@@ -493,9 +515,8 @@ class DonationList(generics.CreateAPIView):
                                      [donation.email])
                                      [donation.email])
                 email.send()
                 email.send()
 
 
-
         # send emails
         # send emails
-        sendDonationEmails(obj)
+        send_donation_emails(obj)
 
 
 
 
 class UserCreateView(views.UserCreateView):
 class UserCreateView(views.UserCreateView):