Pull to refresh
53
0

Software engineer

Send message
Действительно, зачем треш в репе? У меня вообще папка public/ (в ней media и static/ хранятся) находится в .gitignore и создаётся при развёртывании динамически.

Так что тут тоже полностью согласен: трешу в репозитории не место :-)
Вы правы в том, что less и DRY — это хорошо. В остальном — нет :) less нужно компилировать ровно один раз, на своей девелоперской машине, при деплое. Заодно кучу css-файлов можно смержить в один, удалить лишние пробелы и гзипнуть на всякий случай.

С coffeescript проделываются аналогичные вещи: компиляция — мерж — yuicompressor… На выходе получается один минифицированный (или даже обфусцированный) js, который уже и отправляется на сервер.

Такая вот подготовленная статика молча лежит и каши не просит не компилируется. При желании её вообще можно на народ.ру закинуть.

Зачем Вам less на сервере? =)
Зачем катавасия с мемкешем? Если уж единовременно генерировать странички, можно сразу класть их в виде html-файлов в корень nginx'а (кстати, это уже было в Симпсонах). Заодно и память сэкономите, на ВДСах за её расходом приходится следить.

А вообще статья хорошая, конечно =)
Да, чего-то я дал маху с просмотром исходников наискосок.

А Вы лучше не над поддержкой mod_geoip подумайте, а над интерфейсом способа локации. Чтоб и в базе можно было искать, и в бинарном файле и из окружения.

Интерфейсы вообще штука хорошая :-)
Ну так и нужно хранить пользовательскую географию в модели. А если её внезапно не оказалось, или назначать администратором, или пусть юзер при следующем заходе сам инициализирует геолокацию.

А уж как она выполняться будет — от настроек проекта зависит. Хоть sql-поиском в базе IpGeoBase, хоть напрямую из окружения nginx :-)

Ну ё-моё :-)

В request.META будет простое текстовое значение типа RU. Да, его легко вывести в шаблон, но на этом сфера его пользы и заканчивается.

Совсем другое дело django-geoip: {{ request.location }} это полноценная модель, понимаете? Причём кастомизируемая. Её можно и сохранять, и расширять, и даже внешние ключи на неё вешать.

Я пару комментариев назад слово «костыли» по отношению к употребил именно с этой позиции: он может передать лишь текстовую строку, в то время как django-geoip оперирует полноценным питоновским объектом.

Для того, чтобы с тем же комфортом работать с geoip'ом инжиникса, придётся писать много кода (как раз те самые обращения к request.META). Ну, или как меня осинило, предложить ребятам сделать поддержку mod_geoip, благо, насколько я понял их код, это делается элементарно с помощью storage.
Чёрт возьми, а давайте помиримся? :-)

Надо всего лишь предложить ребятам написать бэкенд для получения гео-локации из окружения и тогда мы оба будем правы!
Не поймите меня неправильно, я не спорю о том, что nginx — великолепная штука, встроенная нересурсоёмкая геолокация из коробки — это тоже замечательно и правильно.

Но случай с джангой скорее исключение. Во-первых, она сама по себе достаточно прожорлива, поэтому её запускают обычно на ВДС или в облаках. Но не на виртуальных хостингах.

ВО-вторых, как ни крути, работа с нативными питоновскими объектами удобнее, чем две строки, получаемые из внешней среды.

То есть Вы считаете, что размазать логику на два слоя — Джанги и фронтенда — это вовсе не костыль, а сделать один дополнительный запрос к БД — восьмой смертный грех? =)
Вы правы в том, что когда можно избежать лезть в базу — лучше не лезть. А ведь может случиться так, что информация понадобится не для редиректа на поддомен, а в логике самого проекта. Простейший пример:

<p>
Выберите город, {{ request.user }}. Текущее значение – <b>{{ request.location }}</b>
</p>


Можно получить эту информацию из nginx? Думаю, что можно. Но костыли, применяемые при этом, едва ли будут менее позорны, чем лишний запрос и красивый код :-)
Вы, ребята, молодцы. И блог у вас классный, и распространяемые приложения очень даже.

По поводу этого геолокатора: я правильно понял, что оно не кеширует результаты и при каждом явном обращении к request.location пытается определить, откуда пользователь?
Почему? Это же не миддльварь всё-таки, а именно микшин, хоть и странный.
Давайте всё же попробуем обойтись одним миксином безо всяких пачек? =)

class DecoratorChainingMixin(object):
    def dispatch(self, *args, **kwargs):
        decorators = getattr(self, 'decorators', [])
        base = super(DecoratorChainingMixin, self).dispatch

        for decorator in decorators:
            base = decorator(base)
        return base(*args, **kwargs)

А потом нам потребуется всего лишь перечислить требуемые декораторы в нужном порядке:

class MyView(DecoratorChainingMixin, UpdateItem):
    form_class = ItemForm
    model = Item
    decorators = [login_required, never_cache, i_cherta_lysogo_v_stupe(foo='bar')]


Но это, что называется, низкий уровень. Если хочется абстракций, можно пытаться писать микшины с изменяемым поведением:

class UserPassesTestMixin(object):
    def user_passes_test(self, user):
        return user.is_authenticated()

    def user_failed_test(self):
        return redirect(LOGIN_URL)

    def dispatch(self, request, *args, **kwargs):
        if not self.user_passes_test(request.user):
            return self.user_failed_test()
        return super(UserPassesTestMixin, self).dispatch(request, *args, **kwargs)

Наследуемся и когда потребуется сменить логику проверки доступа пользователя, просто перекрываем user_passes_test. В том маловероятном случае, если не устраивает редирект на страницу логина, можно перекрыть и user_failed_test.
def form_valid(self, form):
        Post.objects.create(**form.cleaned_data)
        return redirect(self.get_success_url())

Это жесть и чревато ошибками. Лучше так:
def form_valid(self, form):
        # Мы используем ModelForm, а его метод save() возвращает инстанс
        # модели, связанный с формой. Аргумент commit=False говорит о том, что
        # записывать модель в базу рановато.
        instance = form.save(commit=False)

        # Теперь, когда у нас есть несохранённая модель, можно ей чего-нибудь
        # накрутить. Например, заполнить внешний ключ на auth.User. У нас же
        # блог, а не анонимный имижборд, правда?
        instance.user = request.user

        # А теперь можно сохранить в базу
        instance.save() 

        return redirect(self.get_success_url())


И ещё: не надоело в каждой вьюхе писать что-то такое?
    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        super(CreatePost, self).dispatch(request, *args, **kwargs)


Особенно нелепо это выглядит на фоне class based, основная идея которого — избавить нас от рутины. Почему бы не сделать миксин, в котором все лишь раз прописать эту магическую формулу, а потом уже наследовать все вьюхи от него?
Это нормально, что в наследнике DetailView метод get_queryset() возвращает объект модели, а не собственно кверисет?
Не то что бы прям канонически правильный, но всё же неплохой пример того, как CBV может облегчить жизнь: github.com/lig/django-supergeneric/
*(а я кланяюсь, кланяюсь)*

class Posts(ListView, MultipleObjectMixin):


Не надо наследоваться от MultipleObjectMixin: ListView уже сделал это за Вас.
Можно вообще все данные отображения свести до одного класса и реализовать классический RESTful.

Мне кажется, вместо «можно» нужно употрелять слово «нужно». Собственно, они для того и писались.

Просто в данной статье хотелось подчеркнуть особенности использования, показания лучшие стороны в ООП подобных отображений

Самое грустное в том, что это неправильный пример. Я бы даже сказал — антипаттерн. Потому что (возможно, повторюсь):

  1. много буквстрок, следовательно «одним взглядом» весь код не охватить и не понять.
  2. написание собственного велосипеда вместо использования готового решения — это я в сторону пагинации. Вы на коленке реализовали то, что уже работает (и лучше :-)) из коробки.
  3. логика разбора кверисета находится совсем не там, где ей надлежит быть
  4. и таки да — перекрытие dispatch. Этого лучше по возможности избегать. Понятно, конечно, что далеко не всегда это пожелание осуществимо, но в данном случае — да.


Практическая полезность этого кода в том, что вы открыли dispatch — точку входа во вьюху. Молодым исследователям будет понятно, с чего начинать. Но я бы сделал приписку: «в чистом виде такой код к применению не рекомендуется» :-)
упс, сразу же работа над ошибками:

  1. в PostView следует читать queryset, а get_queryset
  2. в нём же: не request, а self.request
Мне кажется, что за формирование кверисета get_queryset() должен отвечать. А он тут статичен, зато вся логика по его созданию и изменению в зависимости от условий размазывается в dispatch(), который суть точка входа и желательно его по возможности вообще не трогать.

По-моему, рабочая вьюха с минимальной функциональностью делается так:

class PostView(ListView):
    model = Post

Обратите внимание, никаких перекрытий dispatch() и прочего. Пагинацию тоже не надо использовать низкоуровнево: в ListView, примесью которого является MultipleObjectMixin, она (пагинация) работает по умолчанию (пруф и примеры)

Собственно, весь Ваш код можно переписать компактнее

class PostView(ListView):
    """
    Просмотр списка статей, доступных текущему пользователю
    """
    model = Post
    context_object_name = 'posts'
    template_name = 'posts.html'

    def queryset(self):
        qs = Post.objects.filter(is_delete=False).order_by('-created_at')

        if not request.user.is_authenticated():
            qs = qs.exclude(is_private=True)
        return qs

class PostsIndex(PaginatedList):
    """
    Список статей для главной страницы
    """
    def get_queryset(self):
        return super(PostsIndex, self).get_queryset().exclude(rating__lt=10)


Я не проверял, но мне кажется, что должно быть примерно так :)

А вообще, конечно, погорячились разработчики Джанги с обязательным введением CBV. Начнут ведь теперь лепить классы везде где надо и не надо

Information

Rating
Does not participate
Registered
Activity