Last active
March 13, 2018 12:49
-
-
Save ydm/6712920 to your computer and use it in GitHub Desktop.
Version field for Django models
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
from __future__ import unicode_literals | |
import re | |
from django import forms | |
from django.core import exceptions | |
from django.db import models | |
from south.modelsinspector import add_introspection_rules | |
# TODO: Change this expression to match the correct path | |
add_introspection_rules([], ["^your_app\.version\.VersionField"]) | |
def compare_versions(lhs, rhs): | |
return version_to_int(lhs) - version_to_int(rhs) | |
def version_to_int(s): | |
"""Return an integer that represents given version (X.Y.Z) or raise a | |
ValueError | |
""" | |
parts = s.split('.') | |
ints = [int(parts[i]) if i < len(parts) else 0 for i in range(3)] | |
return sum( | |
[n * m for n, m in zip(ints, [10**6, 10**3, 10**0])] | |
) | |
def int_to_version(i): | |
"""Return the string representation (X.Y.Z) of version integer or raise | |
ValueError | |
""" | |
micro = i % 1000 | |
minor = (i / 1000) % 1000 | |
major = (i / 1000000) % 1000 | |
ver = '.'.join(map(str, [major, minor, micro])) | |
if ver: | |
return re.sub('(\.0)?\.0$', '', ver) # Strip ending zeros | |
else: | |
return '0' | |
def validate_version(value): | |
try: | |
version_to_int(value) | |
except ValueError: | |
msg = "'{}' is not a correct version string".format(value) | |
raise exceptions.ValidationError(msg) | |
class VersionField(models.IntegerField): | |
default_error_messages = { | |
'invalid': "'%s' value must be a correct version string " | |
"(in this form: X.Y.Z).", | |
} | |
default_validators = [validate_version] | |
def to_python(self, value): | |
if value is None: | |
return None | |
if isinstance(value, int) or not '.' in value: | |
try: | |
# If there are not dots in the name, interpret the | |
# integer as major version | |
value = '{:0>3}'.format(value) | |
value = '{:0<9}'.format(value) | |
return int_to_version(int(value)) | |
except ValueError: | |
msg = self.error_messages['invalid'] % value | |
raise exceptions.ValidationError(msg) | |
return value | |
def get_prep_value(self, value): | |
return self.to_python(value) | |
def get_db_prep_value(self, value, connection, prepared=False): | |
if not prepared: | |
value = self.get_prep_value(value) | |
return version_to_int(value) | |
def formfield(self, **kwargs): | |
defaults = {'form_class': forms.CharField} | |
defaults.update(kwargs) | |
return super(VersionField, self).formfield(**defaults) | |
####################### | |
# Example integration # | |
####################### | |
# class Something(models.Model): | |
# some_version = version.VersionField(default='0') | |
# def __init__(self, *args, **kwargs): | |
# super(Message, self).__init__(*args, **kwargs) | |
# fields_iter = iter(self._meta.fields) | |
# if not kwargs: | |
# for val, field in izip(args, fields_iter): | |
# if 'version' in field.attname: | |
# val = version.int_to_version(val) | |
# setattr(self, field.attname, val) | |
# else: | |
# for val, field in izip(args, fields_iter): | |
# if 'version' in field.attname: | |
# val = version.int_to_version(val) | |
# setattr(self, field.attname, val) | |
# kwargs.pop(field.name, None) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Why not compare_versions(old, new)?