Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Switch to mongoengine style defaults. Drop old django #200

Merged
merged 4 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ jobs:
strategy:
matrix:
python:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "pypy3.9"
- "pypy3.10"
django:
- "Django>=3.2,<3.3"
- "Django>=4.2,<4.3"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
8 changes: 4 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Right now we're targeting to get things working on Django 4.2;
WARNING: This project is not in good state, and is likely to break with django updates.
It's better to use raw mongoengine.

Working / Django 3.2-4.2
Working / Django 4.2
------------------------

* [ok] sessions
Expand All @@ -50,9 +50,9 @@ It get's replaced after class creation via some metaclass magick.
Fields notes
------------

* mongo defaults Field(required=False), changed to django-style defaults
-> Field(blank=False), and setting required = not blank in Field.__init__

* Project uses mongoengine style argument `required=False`, not django style `blank=False`,
to be compatible with mongo-types.
**All your fields are optional by default.**


TODO
Expand Down
6 changes: 3 additions & 3 deletions django_mongoengine/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from __future__ import annotations

from functools import partial
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

from bson.objectid import ObjectId
from django.db.models import Model
Expand All @@ -23,7 +23,7 @@
# TopLevelDocumentMetaclass is using ObjectIdField to create default pk field,
# if one's not set explicitly.
# We need to know it's not editable and auto_created.
mtc.ObjectIdField = partial(ObjectIdField, editable=False, auto_created=True, blank=True)
mtc.ObjectIdField = partial(ObjectIdField, editable=False, auto_created=True)


def django_meta(meta, *top_bases):
Expand Down Expand Up @@ -51,7 +51,7 @@ class DjangoFlavor:
objects = QuerySetManager["Self"]()
_default_manager = QuerySetManager["Self"]()
_get_pk_val = Model.__dict__["_get_pk_val"]
_meta: DocumentMetaWrapper
_meta: DocumentMetaWrapper | dict[str, Any]
DoesNotExist: type[DoesNotExist]

def __init__(self, *args, **kwargs):
Expand Down
28 changes: 15 additions & 13 deletions django_mongoengine/fields/djangoflavor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@

from .internal import INTERNAL_DJANGO_FIELDS_MAP

_field_defaults = (
("blank", False),
("null", False),
("help_text", ""),
("editable", True),
("auto_created", False),
)
# Add some default values, required for django.
_field_defaults = {
"help_text": "",
"editable": True,
"auto_created": False,
}


class DjangoField:
Expand All @@ -37,13 +36,13 @@ def _get_flatchoices(self):
flatchoices = property(_get_flatchoices)

def __init__(self, *args, **kwargs):
for k, v in _field_defaults:
kwargs.setdefault(k, v)
if "required" in kwargs:
kwargs = _field_defaults | kwargs

if "blank" in kwargs:
raise ImproperlyConfigured(
"`required` option is not supported. Use Django-style `blank` instead."
"`blank` option is not supported. Use Mongoengine-style `required` instead."
)
kwargs["required"] = not kwargs["blank"]

if hasattr(self, "auto_created"):
kwargs.pop("auto_created")
self._verbose_name = kwargs.pop("verbose_name", None)
Expand All @@ -53,6 +52,9 @@ def __init__(self, *args, **kwargs):

self.remote_field = None
self.is_relation = self.remote_field is not None
# This is needed for django, but we use mongoengine style in __init__
# to not confuse type checker.
self.blank = not self.required

def _get_verbose_name(self):
return self._verbose_name or self.db_field.replace('_', ' ')
Expand Down Expand Up @@ -272,7 +274,7 @@ def formfield(self, **kwargs):
elif isinstance(self.field, fields.ReferenceField):
defaults = {
'form_class': formfields.DocumentMultipleChoiceField,
'queryset': self.field.document_type.objects,
'queryset': self.field.document_type.objects, # type: ignore
}
else:
defaults = {}
Expand Down
44 changes: 9 additions & 35 deletions django_mongoengine/mongo_auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
from django.contrib.contenttypes.models import ContentTypeManager
from django.db import models
from django.utils import timezone
from django.utils.encoding import smart_str
from django.utils.translation import gettext_lazy as _
from mongoengine import ImproperlyConfigured

from django_mongoengine import document, fields
from django_mongoengine.queryset import QuerySetManager

from django.contrib.auth.hashers import check_password, make_password
from .managers import MongoUserManager


Expand All @@ -34,32 +34,6 @@ def ct_init(self, *args, **kwargs):
),
)

try:
from django.contrib.auth.hashers import check_password, make_password
except ImportError:
"""Handle older versions of Django"""
from django.utils.hashcompat import md5_constructor, sha_constructor

def get_hexdigest(algorithm, salt, raw_password):
raw_password, salt = smart_str(raw_password), smart_str(salt)
if algorithm == 'md5':
return md5_constructor(salt + raw_password).hexdigest()
elif algorithm == 'sha1':
return sha_constructor(salt + raw_password).hexdigest()
raise ValueError('Got unknown password algorithm type in password')

def check_password(raw_password, password):
algo, salt, hash = password.split('$')
return hash == get_hexdigest(algo, salt, raw_password)

def make_password(raw_password):
from random import random

algo = 'sha1'
salt = get_hexdigest(algo, str(random()), str(random()))[:5]
hash = get_hexdigest(algo, salt, raw_password)
return '%s$%s$%s' % (algo, salt, hash)


class BaseUser:
is_anonymous = AbstractBaseUser.__dict__['is_anonymous']
Expand Down Expand Up @@ -88,7 +62,7 @@ class Meta:
# ordering = ('name',)
# unique_together = (('app_label', 'model'),)

def __unicode__(self):
def __str__(self):
return self.name

def model_class(self):
Expand Down Expand Up @@ -158,7 +132,7 @@ class Meta:
# unique_together = (('content_type', 'codename'),)
# ordering = ('content_type__app_label', 'content_type__model', 'codename')

def __unicode__(self):
def __str__(self):
return "%s | %s | %s" % (
self.content_type.app_label,
self.content_type,
Expand Down Expand Up @@ -195,7 +169,7 @@ class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')

def __unicode__(self):
def __str__(self):
return self.name


Expand All @@ -208,22 +182,23 @@ class AbstractUser(BaseUser, document.Document):
max_length=150,
verbose_name=_('username'),
help_text=_("Required. 150 characters or fewer. Letters, numbers and @/./+/-/_ characters"),
required=True,
)

first_name = fields.StringField(
max_length=30,
blank=True,
verbose_name=_('first name'),
)

last_name = fields.StringField(max_length=30, blank=True, verbose_name=_('last name'))
email = fields.EmailField(verbose_name=_('e-mail address'), blank=True)
last_name = fields.StringField(max_length=30, verbose_name=_('last name'))
email = fields.EmailField(verbose_name=_('e-mail address'), required=True)
password = fields.StringField(
max_length=128,
verbose_name=_('password'),
help_text=_(
"Use '[algo]$[iterations]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."
),
required=True,
)
is_staff = fields.BooleanField(
default=False,
Expand All @@ -250,7 +225,6 @@ class AbstractUser(BaseUser, document.Document):
user_permissions = fields.ListField(
fields.ReferenceField(Permission),
verbose_name=_('user permissions'),
blank=True,
help_text=_('Permissions for the user.'),
)

Expand All @@ -259,7 +233,7 @@ class AbstractUser(BaseUser, document.Document):

meta = {'abstract': True, 'indexes': [{'fields': ['username'], 'unique': True, 'sparse': True}]}

def __unicode__(self):
def __str__(self):
return self.username

def get_full_name(self):
Expand Down
46 changes: 23 additions & 23 deletions example/tumblelog/tumblelog/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from django.urls import reverse

import datetime

Expand All @@ -13,58 +10,61 @@ class Comment(EmbeddedDocument):
default=datetime.datetime.now,
editable=False,
)
author = fields.StringField(verbose_name="Name", max_length=255)
email = fields.EmailField(verbose_name="Email", blank=True)
body = fields.StringField(verbose_name="Comment")
author = fields.StringField(verbose_name="Name", max_length=255, required=True)
email = fields.EmailField(verbose_name="Email", required=True)
body = fields.StringField(verbose_name="Comment", required=True)


class Post(Document):
created_at = fields.DateTimeField(
default=datetime.datetime.now,
editable=False,
)
title = fields.StringField(max_length=255)
slug = fields.StringField(max_length=255, primary_key=True)
title = fields.StringField(max_length=255, required=True)
slug = fields.StringField(max_length=255, primary_key=True, required=True)
comments = fields.ListField(
fields.EmbeddedDocumentField('Comment'),
fields.EmbeddedDocumentField(Comment, required=True),
default=[],
)
strings = fields.ListField(
fields.StringField(required=True),
default=[],
blank=True,
)

def get_absolute_url(self):
return reverse('post', kwargs={"slug": self.slug})
return reverse("post", kwargs={"slug": self.slug})

def __unicode__(self):
def __str__(self):
return self.title

@property
def post_type(self):
return self.__class__.__name__

meta = {
'indexes': ['-created_at', 'slug'],
'ordering': ['-created_at'],
'allow_inheritance': True,
"indexes": ["-created_at", "slug"],
"ordering": ["-created_at"],
"allow_inheritance": True,
}


class BlogPost(Post):
body = fields.StringField()
body = fields.StringField(required=True)


class Video(Post):
embed_code = fields.StringField()
embed_code = fields.StringField(required=True)


class Image(Post):
image = fields.ImageField()
image = fields.ImageField(required=True)


class Quote(Post):
body = fields.StringField()
author = fields.StringField(verbose_name="Author Name", max_length=255)
body = fields.StringField(required=True)
author = fields.StringField(verbose_name="Author Name", max_length=255, required=True)


class Music(Post):
url = fields.StringField(max_length=100, verbose_name="Music Url")
music_parameters = fields.DictField(verbose_name="Music Parameters")
url = fields.StringField(max_length=100, verbose_name="Music Url", required=True)
music_parameters = fields.DictField(verbose_name="Music Parameters", required=True)
Loading