31.01.2009 13:57

  1. cbx

    0 ↑
    0 ↓
    Понадобилось написать несложное веб-приложение с веб-админкой, начитался восторженных отзывов о django и решил попробовать.

    Цель такая. Нужен список пользователей с паролями (табличка users) и список IP-адресов этих пользователей плюс некоторые атрибуты (табличка addresses), отношение 1 ко многим.

    Пользователи должны логиниться на отдельной страничке и им по выбранному из базы списку их адресов будет показываться статистика (вопросы хранения и извлечения статистики не важны в данном примере, можно считать что пользователь просто увидит список своих адресов и всё).

    Также должен быть администратор (или администраторы) которые могут добавлять логины/пароли пользователей и указывать список их адресов.

    Третий компонент - cron job которому надо читать из базы список пользователей и их адресов.

    Проблема в том что я не дружу с OOP и испытываю сложности с пониманием того как работают написанные кем-то сложные объектные модели. Вопросы пока такие:

    1. Должен ли я создать свою модель users или допустимо использование единой базы django.contrib.auth? Не появится ли во втором случае у пользователей какого-либо незапланированного мной функционала доступного по умолчанию? Если возможна работа на базе штатного модуля, как при необходимости добавить к нему дополнительные поля?

    2. Я хочу хранить IP адрес в виде INT UNSIGNED (mysql) и для преобразования в читабельный вид использовать inet_ntoa и inet_aton (это позволит мне при необходимости легко выбирать диапазон адресов). Как сделать чтобы админка django с этим нормально работала (выполняла необходимые преобразования)? Также для удобной забивки базы хотелось бы в том же окне редактирования таблицы addresses (для конкретного юзера) иметь поле в которое можно было ввести диапазон в виде 123.45.67.89/24 а затем проверив его на валидность и преобразовав в последовательность адресов вставить или удалить из таблицы (и всё это не теряя штатных функций админки по редактированию таблицы).

    3. Как в отдельном не интерактивном скрипте (вызываемом по крону) подключить и использовать ORM django (и ничего лишнего) для обращения к базе?
    1. Да, можно(и даже нужно) использовать. Не будет хватать полей и возможностей. всегда от него можно отнаследоваться или воспользоваться системой профилей.

    2. Для этого пишется свои поля, как для модели так и для формы(ещё и с виджетом своим), где можно предусмотреть все нужные валидации и действия.

    3. Наждо настроить оружение джанги для этого есть несколько способов, как описаных в документации, так и в мнгочисленных постах блоггеров.

  2. Добро пожаловать!

    1. Должен ли я создать свою модель users или допустимо использование единой базы django.contrib.auth? Не появится ли во втором случае у пользователей какого-либо незапланированного мной функционала доступного по умолчанию? Если возможна работа на базе штатного модуля, как при необходимости добавить к нему дополнительные поля?

    Использование стандартной модели User сильно рекомендуется. По умолчанию юзер может только логиниться, все дополнительные действия (доступ в админку, редактирование моделей, специальные права) должны разрешаться ему явно (в той же админке).

    По поводу дополнительных полей есть по большому счету три разных способа:

    • дописывание атрибутов к модели в рантайме ("манки-патчинг")
    • наследование от модели User своей модели юзера
    • привязывание своей модели профиля к User отношением 1-1

    Я бы ни в коем случае не рекомендовал пользоваться первым способом. Он многими любим за кажущуюся простоту и ковбойскую крутость, но это дорога к maintenance hell, наложению полей разных приложений и прочим "прелестям" неявной магии.

    Второй и третье физически одно и то же — отдельная таблица с дополнительными полями, привязанная к User 1-1. Разница только синтаксическая. Мне, например, удобно думать об этих данных как об отдельном профиле, свойственном приложению, а не как о расширенном User'е.

    1. Я хочу хранить IP адрес в виде INT UNSIGNED (mysql) и для преобразования в читабельный вид использовать inetntoa и inetaton (это позволит мне при необходимости легко выбирать диапазон адресов). Как сделать чтобы админка django с этим нормально работала (выполняла необходимые преобразования)?

    Вообще, в Джанге есть IPAdressField. Правда, под MySQL это переводится в varchar(15), поэтому вам не подойдет. Чтобы хранить это целым числом, надо будет написать наследника от PositiveIntegerField'а и написать ему две функции преобразования: из строки в число и обратно. Про то, как это делать, есть подробная инструкция: http://docs.djangoproject.com/en/dev/howto/custom-model-fields/. Она большая, потому что подробная, а не потому что сложная :-)

    Также для удобной забивки базы хотелось бы в том же окне редактирования таблицы addresses (для конкретного юзера) иметь поле в которое можно было ввести диапазон в виде 123.45.67.89/24 а затем проверив его на валидность и преобразовав в последовательность адресов вставить или удалить из таблицы (и всё это не теряя штатных функций админки по редактированию таблицы).

    Для этого форму редактирования надо заменить своей, в которую будет добавлено такое поле. Тоже штатное средство, но на память я не помню, описано ли это где-то подробно. Если соберетесь делать, напишите, расскажем.

    1. Как в отдельном не интерактивном скрипте (вызываемом по крону) подключить и использовать ORM django (и ничего лишнего) для обращения к базе?

    Оформить его в виде команды Джанго. Тогда в скрипте сразу будет нужная среда.

  3. cbx

    0 ↑
    0 ↓
    Спасибо за подробные ответы!

    Написал модель, вроде даже валидацию делать не пришлось из-за того что выдаю в качестве form_class стандартный IPAddressField:

    
    from django.db import models
    from socket import inet_aton, inet_ntoa
    from struct import pack, unpack
    
    ROUTER_CHOICES = (
            ('R1', 'Router 1'),
            ('R2', 'Router 2'),
            ('R3', 'Router 3')
    )
    
    IPTYPE_CHOICES = (
            ('R', 'Real'),
            ('P', 'Private')
    )
    
    class IPField(models.Field):
            empty_strings_allowed = False
    
            __metaclass__ = models.SubfieldBase
    
            def get_db_prep_value(self, value):
    #               print "to db", type(value), value
                    if value is None: return None
                    return unpack('!L', inet_aton(value))[0]
    
            def get_internal_type(self):
                    return "PositiveIntegerField"
    
            def to_python(self, value):
                    if type(value).__name__ in ('NoneType', 'unicode'): return value
                    try:
    #                       print "to py", type(value), value
                            return inet_ntoa(pack('!L', value))
                    except (TypeError, ValueError):
                            raise models.exceptions.ValidationError("IP address cannot be converted to string.")
    
            def formfield(self, **kwargs):
                    defaults = {'form_class': models.forms.IPAddressField}
                    defaults.update(kwargs)
                    return super(IPField, self).formfield(**defaults)
    
    class Addresses(models.Model):
            router = models.CharField("Router ID", max_length=3, choices=ROUTER_CHOICES)
            address = IPField("IP address")
            adrtype = models.CharField("Type", max_length=1, choices=IPTYPE_CHOICES)
    


    В admin.py пришлось сделать list_display = ('address', 'adrtype', 'router') чтобы показывались колонки а не абстрактный Address object. Кстати, правильно ли я понял что в современном Django админка описывается в отдельном файле и в модель уже нельзя добавлять класс Admin?

    В каких направлениях теперь нужно смотреть чтобы привести админку в юзабельное состояние?

    Для начала как делается связка с таблицей пользователей (и как вообще в джанго принято организовывать редактирование подобных связок)? Скажем если бы я делал всё на PHP, возможно переход в редактор addresses у меня был бы по клику на строке из users, либо подумал насчёт варианта размещения обеих форм на одной странице.

    Ну и хотелось бы в разделе admin/clientdb/addresses/ иметь возможность групповых операций с помощью отдельного поля ввода cidr нотации и возможно пометки записей для последующего удаления или массовой смены одного из атрибутов. Также было бы удобно непосредственное редактирование полей типа CHOICE в таблице.

    EDIT: если кому-то пригодится вышеприведённый код, то будьте внимательны - в нём не хватает метода get_db_prep_lookup который должен уметь выполнять преобразования типов для работоспособности поиска по этому полю
  4. Кстати, правильно ли я понял что в современном Django админка описывается в отдельном файле и в модель уже нельзя добавлять класс Admin?

    Ага, она теперь совсем отделена от определения моделей.

    В каких направлениях теперь нужно смотреть чтобы привести админку в юзабельное состояние?

    http://docs.djangoproject.com/en/dev/ref/contrib/admin/

    Для начала как делается связка с таблицей пользователей (и как вообще в джанго принято организовывать редактирование подобных связок)

    Это два отдельных вопроса.

    Чтобы связать модели, нужно в Address написать ForeignKey на User:

    from django.contrib.auth.models import User
    
    class Address(models.Model):
        user = models.ForeignKey(User)
        router = models.CharField("Router ID", max_length=3, choices=ROUTER_CHOICES)
        address = IPField("IP address")
        adrtype = models.CharField("Type", max_length=1, choices=IPTYPE_CHOICES)
    

    И кстати, принято писать названия классов моделей в единственном числе, так потом все естественней выглядит. Соответственно, после такого объявления не только у каждого объекта Address будет ссылка address.user, но и у каждого юзера будет ссылка user.address_set. Если не нравится автоматическое название "address_set", можно указать свое:

    user = models.ForeignKey(User, related_name='addresses')
    

    В админке это можно редактировать двояко: либо как отдельную модель адресов, либо как список адресов внутри модели юзера. Второе делается через inline-модели (в документашке выше есть).

    Ну и хотелось бы в разделе admin/clientdb/addresses/ иметь возможность групповых операций

    Вот групповых операций в штатной админке пока нет. В принципе, все дописываемо извне некими хаками, но тут я не специалист.

  5. cbx

    0 ↑
    0 ↓
    Иван, спасибо за выделенное на ответы время. Ещё один вопрос, и я пошёл читать документацию :)

    Насколько вот это описание http://www.b-list.org/weblog/2006/jun/13/how-django-processes-request/ в целом соответствует архитектуре текущего Django (у меня 1.0.2)?
  6. Соответствует полностью. Эта архитектура — пожалуй и есть суть Джанго. Она не меняется с момента открытия фреймворка в 2005 году.

  7. Отщеплен новый топик "Развёртывание: apache + mod_wsgi".
  8. cbx

    0 ↑
    0 ↓

    В админке это можно редактировать двояко: либо как отдельную модель адресов, либо как список адресов внутри модели юзера. Второе делается через inline-модели (в документашке выше есть).


    Сделал и так и так. В модели юзера для удобства свернул все fieldset'ы кроме первого чтобы не загромождало страницу. Правда не очень нравится что пришлось скопировать параметр fieldsets из UserAdmin вместе с импортом ugettext_lazy. Можно как-то в MyUserAdmin модифицировать fieldsets класса-предка и проставить им 'classes'='collapse' не трогая ничего более? И кстати правильно ли я понял, что fieldsets это свойство класса а не его экземпляра?


    Для этого форму редактирования надо заменить своей, в которую будет добавлено такое поле. Тоже штатное средство, но на память я не помню, описано ли это где-то подробно. Если соберетесь делать, напишите, расскажем.


    Теперь собственно про групповые операции... На данный момент через inline редактор всё несколько удобнее нежели непосредственное редактированием модели, т.к. в TabularInline всё достаточно компактно расположено и сравнительно небольшим количеством кликов я могу изменить поля типа choice или удалить несколько записей одновременно.

    Однако при большом количестве записей начинает мешать осутствие фильтров и поиска по inline-модели, поэтому решил посмотреть в сторону расширения режима просмотра модели в виде списка (myapp/mymodel/, шаблон change_list.html). Пока что из стандартных вещей добавил поиск по адресу и фильтр по полям с небольшим количеством вариантов (router, adrtype). Добавление фильтра по имени пользователя не подошло, т.к. список пользователей может быть достаточно большим. Далее вопросы по увеличению удобства работы с большим числом записей такие:

    * как поле user сделать ссылкой на inline-редактор (auth/user/<id>/)
    * возможно ли создание своего фильтра с полем типа text и собственной логикой выборки записей из таблицы на основе этого поля?
    * сузив при помощи фильтров выборку до нужного диапазона, неплохо бы иметь возможность пометки строк таблицы с помощью checkbox'ов
    * добавить под таблицей свою форму и кнопки для группового изменения атрибутов выбранного подмножества, плюс форму добавления подсети

    Ещё из мелочей не понял каким образом можно в разделе добавления или редактирования записи модели (разделы админки myapp/mymodel/add/ и myapp/mymodel/<id>/) расположить поля в строку горизонтально.
  9. Можно как-то в MyUserAdmin модифицировать fieldsets класса-предка и проставить им 'classes'='collapse' не трогая ничего более?

    В принципе можно. Написать к своему классу метакласс, в котором пробежаться по унаследованным атрибутам и их поменять. Но на практике я бы не стал так делать. Настройка админки довольно статичная вещь и пользы от реюза там немного, поэтому тут copy-paste, мне кажется, самое то.

    И кстати правильно ли я понял, что fieldsets это свойство класса а не его экземпляра?

    Да. Раз оно прописано в классе, а не в __init__ создается — это атрибут класса.

    как поле user сделать ссылкой на inline-редактор (auth/user/<id>/)

    В change_list можно выводить не только поле, но и любой атрибут. Сделайте у модели атрибут, который будет выдавать наружу HTML со ссылкой:

    class MyModel(models.Model):
        # ...
    
    def user_link(self):
        return '<a href="%s">%s</a>' % ('/admin/auth/user/' % self.user_id, self.user)
    user_link.allow_tags = True
    

    возможно ли создание своего фильтра с полем типа text и собственной логикой выборки записей из таблицы на основе этого поля?

    У нследника AdminSite нужно переопределить метод queryset, который получает в параметрах request, из которого можно вытащить любые GET-параметры и как угодно дофильтровать возвращаемый queryset.

    • сузив при помощи фильтров выборку до нужного диапазона, неплохо бы иметь возможность пометки строк таблицы с помощью checkbox'ов
    • добавить под таблицей свою форму и кнопки для группового изменения атрибутов выбранного подмножества, плюс форму добавления подсети

    Посмотрите на batchadmin. Сам я с ним не работал, но народ отзывается хорошо.

  10. cbx

    0 ↑
    0 ↓

    В принципе можно. Написать к своему классу метакласс, в котором пробежаться по унаследованным атрибутам и их поменять.


    К сожалению концепция метаклассов пока для меня малопонятна... Я сделал вот так, правда не уверен что побочных эффектов нет:

    
    class MyUserAdmin(UserAdmin):
            fieldsets = tuple((f[0], f[1] if f[0] is None else dict([(k, v) for k, v in f[1].iteritems()]+[('classes', ('collapse',))]) or f[1]) for f in UserAdmin.fieldsets)
           inlines = [AddressAdminInline, ] 
    


    По крайней мере после такой операции у родителя fieldsets не поменялся.

    За batchadmin спасибо, пошёл смотреть. Если и не целиком использую, то хотя бы как там всё сделано погляжу.

    И ещё попутно нашёл пару примеров по написанию собственных FilterSpec:
    http://www.djangosnippets.org/snippets/1051/
    http://tilarids.blogspot.com/2008/03/django-custom-filterspecs.html

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.