Passed
Pull Request — master (#10)
by
unknown
01:51
created

LocalizedUniqueSlugField._new_save()   B

Complexity

Conditions 7

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7.0119

Importance

Changes 0
Metric Value
cc 7
c 0
b 0
f 0
dl 0
loc 19
ccs 15
cts 16
cp 0.9375
crap 7.0119
rs 7.3333
1 1
from datetime import datetime
0 ignored issues
show
Bug introduced by
There seems to be a cyclic import (localized_fields -> localized_fields.models -> localized_fields.fields -> localized_fields.fields.localized_autoslug_field -> localized_fields.fields.localized_field).

Cyclic imports may cause partly loaded modules to be returned. This might lead to unexpected runtime behavior which is hard to debug.

Loading history...
Bug introduced by
There seems to be a cyclic import (localized_fields -> localized_fields.models -> localized_fields.fields -> localized_fields.fields.localized_bleach_field -> localized_fields.fields.localized_field).

Cyclic imports may cause partly loaded modules to be returned. This might lead to unexpected runtime behavior which is hard to debug.

Loading history...
Bug introduced by
There seems to be a cyclic import (localized_fields -> localized_fields.fields -> localized_fields.fields.localized_autoslug_field -> localized_fields.fields.localized_field).

Cyclic imports may cause partly loaded modules to be returned. This might lead to unexpected runtime behavior which is hard to debug.

Loading history...
Bug introduced by
There seems to be a cyclic import (localized_fields -> localized_fields.models -> localized_fields.fields -> localized_fields.fields.localized_uniqueslug_field -> localized_fields.fields.localized_field).

Cyclic imports may cause partly loaded modules to be returned. This might lead to unexpected runtime behavior which is hard to debug.

Loading history...
2
3 1
from django.conf import settings
0 ignored issues
show
Configuration introduced by
The import django.conf could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
4 1
from django import forms
0 ignored issues
show
Configuration introduced by
The import django could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
5 1
from django.utils.text import slugify
0 ignored issues
show
Configuration introduced by
The import django.utils.text could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
6 1
from django.db import transaction
0 ignored issues
show
Configuration introduced by
The import django.db could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
7 1
from django.db.utils import IntegrityError
0 ignored issues
show
Configuration introduced by
The import django.db.utils could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
8
9
10 1
from ..util import get_language_codes
11 1
from ..localized_value import LocalizedValue
12 1
from .localized_field import LocalizedField
13
14
15 1
class LocalizedUniqueSlugField(LocalizedField):
16
    """Automatically provides slugs for a localized field upon saving."
17
18
    An improved version of :see:LocalizedAutoSlugField,
19
    which adds:
20
21
        - Concurrency safety
22
        - Improved performance
23
24
    When in doubt, use this over :see:LocalizedAutoSlugField.
25
    """
26
27 1
    def __init__(self, *args, **kwargs):
28
        """Initializes a new instance of :see:LocalizedUniqueSlugField."""
29
30 1
        kwargs['uniqueness'] = kwargs.pop('uniqueness', get_language_codes())
31
32 1
        self.populate_from = kwargs.pop('populate_from')
33 1
        self.include_time = kwargs.pop('include_time', False)
34
35 1
        super().__init__(*args, **kwargs)
36
37 1
    def deconstruct(self):
38
        """Deconstructs the field into something the database
39
        can store."""
40
41 1
        name, path, args, kwargs = super(
42
            LocalizedUniqueSlugField, self).deconstruct()
43
44 1
        kwargs['populate_from'] = self.populate_from
45 1
        kwargs['include_time'] = self.include_time
46 1
        return name, path, args, kwargs
47
48 1
    def formfield(self, **kwargs):
49
        """Gets the form field associated with this field.
50
51
        Because this is a slug field which is automatically
52
        populated, it should be hidden from the form.
53
        """
54
55 1
        defaults = {
56
            'form_class': forms.CharField,
57
            'required': False
58
        }
59
60 1
        defaults.update(kwargs)
61
62 1
        form_field = super().formfield(**defaults)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
63 1
        form_field.widget = forms.HiddenInput()
64
65 1
        return form_field
66
67 1
    def contribute_to_class(self, cls, name, *args, **kwargs):
68
        """Hook that allow us to operate with model class. We overwrite save()
69
        method to run retry logic.
70
71
        Arguments:
72
            cls:
73
                Model class.
74
75
            name:
76
                Name of field in model.
77
        """
78
        # apparently in inheritance cases, contribute_to_class is called more
79
        # than once, so we have to be careful not to overwrite the original
80
        # save method.
81 1
        if not hasattr(cls, '_orig_save'):
82 1
            cls._orig_save = cls.save
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _orig_save was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
83 1
            max_retries = getattr(
84
                settings,
85
                'LOCALIZED_FIELDS_MAX_RETRIES',
86
                100
87
            )
88
89 1
            def _new_save(instance, *args_, **kwargs_):
90 1
                retries = 0
91 1
                while True:
92 1
                    with transaction.atomic():
93 1
                        try:
94 1
                            slugs = self.populate_slugs(instance, retries)
95 1
                            setattr(instance, name, slugs)
96 1
                            instance._orig_save(*args_, **kwargs_)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _orig_save was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
97 1
                            break
98 1
                        except IntegrityError as e:
99 1
                            if retries >= max_retries:
100 1
                                raise e
101
                            # check to be sure a slug fight caused
102
                            # the IntegrityError
103 1
                            s_e = str(e)
104 1
                            if name in s_e and 'unique' in s_e:
105 1
                                retries += 1
106
                            else:
107
                                raise e
108
109 1
            cls.save = _new_save
110 1
        super().contribute_to_class(cls, name, *args, **kwargs)
111
112 1
    def populate_slugs(self, instance, retries=0):
113
        """Built the slug from populate_from field.
114
115
        Arguments:
116
            instance:
117
                The model that is being saved.
118
119
            retries:
120
                The value of the current attempt.
121
122
        Returns:
123
            The localized slug that was generated.
124
        """
125 1
        slugs = LocalizedValue()
126 1
        populates_slugs = getattr(instance, self.populate_from, {})
127 1
        for lang_code, _ in settings.LANGUAGES:
128
129 1
            value = populates_slugs.get(lang_code)
130
131 1
            if not value:
132 1
                continue
133
134 1
            slug = slugify(value, allow_unicode=True)
135
136
            # verify whether it's needed to re-generate a slug,
137
            # if not, re-use the same slug
138 1
            if instance.pk is not None:
139 1
                current_slug = getattr(instance, self.name).get(lang_code)
0 ignored issues
show
Bug introduced by
The Instance of LocalizedUniqueSlugField does not seem to have a member named name.

This check looks for calls to members that are non-existent. These calls will fail.

The member could have been renamed or removed.

Loading history...
140 1
                if current_slug is not None:
141 1
                    stripped_slug = current_slug[0:current_slug.rfind('-')]
142 1
                    if slug == stripped_slug:
143 1
                        slugs.set(lang_code, current_slug)
144 1
                        continue
145
146 1
            if self.include_time:
147 1
                slug += '-%d' % datetime.now().microsecond
148
149 1
            if retries > 0:
150
                # do not add another - if we already added time
151 1
                if not self.include_time:
152 1
                    slug += '-'
153 1
                slug += '%d' % retries
154
155 1
            slugs.set(lang_code, slug)
156
        return slugs
157