Skip to content

Instantly share code, notes, and snippets.

@ydm
Last active March 13, 2018 12:49
Show Gist options
  • Save ydm/6712920 to your computer and use it in GitHub Desktop.
Save ydm/6712920 to your computer and use it in GitHub Desktop.
Version field for Django models
# -*- 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)
@lean0708
Copy link

Why not compare_versions(old, new)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment