Passed
Push — master ( 8b08e5...a9037b )
by Swen
02:03
created

LocalizedUniqueSlugField.contribute_to_class()   F

Complexity

Conditions 9

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9.0076

Importance

Changes 0
Metric Value
cc 9
c 0
b 0
f 0
dl 0
loc 44
ccs 21
cts 22
cp 0.9545
crap 9.0076
rs 3

1 Method

Rating   Name   Duplication   Size   Complexity  
B LocalizedUniqueSlugField._new_save() 0 19 7
1 1
from datetime import datetime
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