Naum74
Naum74Қар. 13, 2020, 8:35 Т.Ж.

Django раздача прав для заполнения столбцов в таблице данных admin

admin, Django, permissions, table

Добрый всем день, Добрые Люди!
Прошу помощи в деле для вас несложном и даже наверное простом. Дело в том, что пытаюсь сделать разграничение прав в таблице которая в админке django.
У меня есть несколько групп пользователей. Пользователи первой группы должны будут заполнять только колонки свой группы (например в с 1 по 3-ю), пользователи 2-й группы (должны заполнять только колонки с 4-й по 8-ю) и пользователи 3-й группы должны будут заполнять колонки только с 9-й и по крайнюю.
Ни один из пользователей не обладает правами суперпорльзователя, который один и весь процесс просто контролирует.

Как мне это сделать?

Дело в том, что пользователи 1-й группы создают строки в таблице в которых присутствовать должны все столбцы которые им можно заполнять и которые нельзя заполнять.
Иными словами, пользователи первой группы не могут вносить корректировки в столбцы которые заполняют пользователи 2-1 и третьей группы. Так же пользователи 2-й и 3-й группы могут видеть все столбцы как и пользователи 1-й группы, но не должны их изменять.
Видимо решение кроется в определение к какой группе какой пользователь относится? И еще наверное выполнение условия через оператор IF? Но пока я не могу найти приемлемого решения уже очень долго.

вот мой models.py:

from django.db import models

class Articles(models.Model):
    n = models.CharField('Номер заказа', max_length=20)
    date = models.DateTimeField('Дата создания заказа', auto_now_add=True)
    title3 = models.DateTimeField('Дата внесения изменения', auto_now=True)
    title4 = models.CharField('Контрагент', max_length=50)
    title5 = models.CharField('Адрес поставки', max_length=80, default='---')
    title6 = models.CharField('Наименование', max_length=50, default='---')
    title7 = models.CharField('Фасовка', max_length=25, default='---')
    title10 = models.CharField('Выгрузка', max_length=15, default='---')
    title11 = models.DateTimeField('Дата отгрузки', blank=True, null=True)
    title12 = models.IntegerField('Целевая цена доставки (руб.)', default='---', blank=True, null=True)
    title13 = models.CharField('ДС подписано', max_length=3, default='---')
    title14 = models.CharField('Тип доставки', max_length=25, default='---')
    title16 = models.DateTimeField('Дата производства', blank=True, null=True)
    title17 = models.CharField('день или ночь', max_length=5, default='---', blank=True)
    title18 = models.CharField('Готовность продукта', max_length=3, default='---')
    title19 = models.CharField('Готовность фасовки', max_length=3, default='---')
    title20 = models.CharField('Порядок загрузки', max_length=15, default='---', blank=True)
    title21 = models.DateTimeField('Машина заказана на дату', blank=True, null=True)
    title22 = models.DateTimeField('Дата отправки факт', blank=True, null=True)

        def __str__(self):
        return str(self.n)

    class Meta:
        verbose_name = '=ЗАКАЗЫ='
        verbose_name_plural = '=ЗАКАЗЫ='

далее мой admin.py:

from .models import Article


class ExcelsAdmin(admin.ModelAdmin):

    list_display = ['id', 'n', 'user', 'group', 'date', 'title3', 'title4', 'title5', 'title6', 'title7',
                    'title8', 'category', 'title10', 'title11', 'title12', 'title13', 'title14',
                    'title16', 'title17', 'title18', 'title19', 'title20', 'title21',
                    'title22', 'title23', 'title24', 'title25', 'title26',
                    ] 


    search_fields = ['id', 'n', 'title4', 'title5', 'title6', 'title7',
                     'title10', 'title13', 'title14',
                     'title17', 'title18', 'title19', 'title20',
                     'title23', 'title24',
                     ] 

    list_filter = ('title4', 'title5', 'title6', 'title7',
                   'category',) 

admin.site.register(Articles,
                    ExcelsAdmin)

Спасибо всем неравнодушным и знающим решение! Делаю чтобы помочь человеку который сам не может это сделать!

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

9
Илья Чичак
  • Қар. 13, 2020, 8:48 Т.Ж.
  • (өңделген)

1) title[1-22] - крайне неудачный вариант. Лучше дать осмысленные названия, потом будет проще
2) в модели можно сделать разрешения:

class Articles(model.Model):
    ...

    class Meta:
        permissions = (
            ('can_edit_first_group', 'Can edit first group of columns'),
            ('can_edit_second_group', 'Can edit second group of columns'),
            ('can_edit_third_group', 'Can edit third group of columns'),
        )

после этого создать миграцию и выполнить ее

появятся эти права. каждое право присвоить своей группе

в админке сделать метод:

class ArticleAdmin(admin.ModelAdmin)
    first_group_fields = ('...', '...', '...')
    second_group_fields = ('...', '...', '...')
    ...

    def get_readonly_fields(self, request, obj=None)
        base_readonly_fields = super().get_readonly_fields(request, obj)
        if request.user.has_perm('can_edit_first_group'):
            fields = [f for f in base_readonly_fields if f in self.first_group_fields]
        elif request.user.has_perm('can_edit_second_group'):
            fields = [f for f in base_readonly_fields if f in self.second_group_fields]
        ...
        return fields
    Naum74
    • Қар. 14, 2020, 5:57 Т.Ж.

    Илья, ЗДРАВСТВУЙТЕ!
    Вы подтверждаете истину, что мир не без добрых людей и вы среди них один из первых!
    ОРГОМНОЕ ВАМ СПАСИБО!
    Ранее я тоже смотрел на это решение через добавление прав, но почему-то оно паказалось мне неспособным решить мою задачу. Видимо по моему малому знанию самого предмета изучения.
    Спасибо ВАМ еще раз!
    У меня вопрос к ВАМ по поводу метода:
    - first_group_fields = ('...', '...', '...') - тут прописывать именно те столбцы которые должны быить видны той группе которая должна их редактировать?

    Обязательно восспользуюсь Вашим советом по переименованию столбцов - действительно просто их пронумеровать это путано и неудобно!
    Сейчас не у того компа на котором сама программа и не могу попробовать вами предложенный код в работе.
    Но в понедельник надеюсь сделаю, исправлю и пришлю отзыв!
    Вы мне очень помогли! Сам бы я до такого ОЧЕНЬ долго доходил!

      Илья Чичак
      • Қар. 14, 2020, 6:17 Т.Ж.

      про first_group_fields = ('...', '...', '...') - можно пойти двумя путями:

      1) указать поля, которые доступны первой группе пользователей
      2) указать поля, которые НЕ доступны первой группе пользователей

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

      если хочется больше гибкости, лучше воспользоваться вторым способом:

      class ArticleAdmin(admin.ModelAdmin)
          first_group_disallowed_fields = ('...', '...', '...')
          second_group_disallowed_fields = ('...', '...', '...')
          ...
      
          def get_readonly_fields(self, request, obj=None)
              base_readonly_fields = super().get_readonly_fields(request, obj)
              if request.user.has_perm('can_edit_first_group'):
                  fields = [f for f in base_readonly_fields if f not in self.first_group_disallowed_fields]
              elif request.user.has_perm('can_edit_second_group'):
                  fields = [f for f in base_readonly_fields if f not in self.second_group_disallowed_fields]
              ...
              return fields
      
      

      таким образом, получится некоторое дублирование полей:
      для первой группы не доступны поля: поле5, поле6, поле7 и т.д.
      для второй - поле1, поле2, поле3 и т.д.
      для третьей - поле1, поле2, поле3, поле4, поле9 и т.д.

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

      так же отмечу, что выфильтровывание полей я предлагаю потому, что если случится ошибка в перечислении полей - ничего не случится, просто оно будет недоступно в первом случае, либо доступно во втором.

      Оба подхода дают контроль и тут все решается вашим подходом - в первом случае вы явно задаете поля, к которым есть доступ (и ни к каким больше доступа нет). все, что не разрешено - запрещено
      во втором вы задаете поля, к которым нет доступа. все, что не запрещено - разрешено.

        Илья Чичак
        • Қар. 14, 2020, 6:41 Т.Ж.
        • (өңделген)

        ну и если удариться в лучшие практики, я бы посоветовал:
        1) использовать декоратор а не admin.site.register(Articles, ExcelsAdmin):

        @admin.register(Articles)
        class ExcelsAdmin(admin.ModelAdmin):
            ...
        

        это позволит проще искать определение класса админки
        2) не засовывать много полей в search_fields - это будет очень тяжелый запрос, который будет очень долго выполняться и при его выполнении будет большое использование памяти. Лучше таки определить ключевые поля поиска и использовать их
        Так же стоит отметить, что полнотекстовый поиск в БД - крайне неэффективный.
        3) list_display - тоже. понятно, что это будет происходить относительно быстро и не будет создавать нагрузку на БД, поскольку получаться из базы будут объекты целиком, НО это будет крайне сложно использовать - большая часть полей будет вне поля видимости экрана и придется скроллить
        4) я бы переписал модель таким образом:

        class Articles(models.Model):
            n = models.CharField('Номер заказа', max_length=20)
            created_at = models.DateTimeField('Дата создания заказа', auto_now_add=True)
            modified_at = models.DateTimeField('Дата внесения изменения', auto_now=True)
            contragent = models.CharField('Контрагент', max_length=50)
            delivery_address = models.CharField('Адрес поставки', max_length=80, default='')
            label = models.CharField('Наименование', max_length=50, default='')
            packing = models.CharField('Фасовка', max_length=25, default='')
            output = models.CharField('Выгрузка', max_length=15, default='')
            output_at = models.DateTimeField('Дата отгрузки', blank=True, null=True)
            price = models.DecimalField('Целевая цена доставки (руб.)', blank=True, null=True)
            is_signed = models.BooleanField('ДС подписано', default=False)
            delivery_type = models.CharField('Тип доставки', max_length=25, default='')
            production_date = models.DateTimeField('Дата производства', blank=True, null=True)
            day_part = models.CharField('день или ночь', max_length=5, default='', blank=True)
            product_is_ready = models.BooleanField('Готовность продукта', default=False)
            pack_is_ready = models.BooleanField('Готовность фасовки', default=False)
            packing_order = models.CharField('Порядок загрузки', max_length=15, default='', blank=True)
            delivery_planed_at = models.DateTimeField('Машина заказана на дату', blank=True, null=True)
            send_at = models.DateTimeField('Дата отправки факт', blank=True, null=True)
        
            def __str__(self):
                return str(self.n)
        
            class Meta:
                verbose_name = '=ЗАКАЗ='
                verbose_name_plural = '=ЗАКАЗЫ='
        

        случше использовать default='' для полей, которые могут быть пустыми.

        5) так же почитайте про choices - в некоторых полях оно прям напрашивается:

        class Articles(models.Model):
            DELIVERY_TYPE_PRIMARY = 'primary'
            DELIVERY_TYPE_SECONDARY = 'secondary'
        
            DELIVERY_TYPES = (
                (DELIVERY_TYPE_PRIMARY, 'Первоочередная'),
                (DELIVERY_TYPE_SECONDARY, 'По остаточному принципу')
            )
        
            ...
            delivery_type = models.CharField('Тип доставки', choices=DELIVERY_TYPES, max_length=25, default='')
            ...
        
        

        в таком случае будет сильно проще использовать фильтрации:

        Articles.objects.filter(delivery_type=Articles.DELIVERY_TYPE_PRIMARY)
        

        6) контрагента я бы вынес в отдельную модель, а ссылался бы на нее через ForeignKey
        будет еще удобнее:

        class Contragent(models.Model):
            name = models.CharField(max_length=100)
            address = models.CharField(max_length=250)
            ...
        
        class Articles(models.Model):
            ...
            contragent = models.ForeignField(Contragent, related_name='contragents', on_delete=models.CASCADE)
        

        тогда будет просто получать все заказы по контрагенту

        в целом, почитайте про нормализации БД - будет сильне проще работать с такой структурой

          Naum74
          • Қар. 14, 2020, 7:57 Т.Ж.

          Вы просто посланы мне наверное внаграду за мои немногочисленные добрые дела!

          Потому как другу, которому хочу помочь, это просто единственный шанс сделать для него доброе дело, так как он для меня много чего сделал, а тут он ОДИН раз в жизни попросил и мне нельзя просто ему не помочь!

          Теперь по делу:
          - я обязательно "переварю", что вы мне великодушно предлагаете и у меня будут вопросы.
          Дело в том, что я нашел еще один код который мне помогает сделат пользователей Первой группы разделенными по принципц авторства, а именно по тому, чnо каждый автор Заказа (строки с колонками) видит только свой Заказ который он создал и не видит Заказ который создал другой пользователь первой группы.

          Но тут же вопрос в том как сделать чтобы пользователи 2-й и 3-й группы видели все Заказы первой группы пользователей???

          Естественно суперадмин видит все и обладает полными правами по редактированию и добавлению всего!
          Вот у меня код который делает видимость пользователей первой группы по авторству (код не мой, но работает, я его немного подправил от первоначального вида по функции get_queryset (в исходнике дыло queryset)) :

          user_fieldsets = (
               (None, {
                   'classes': ('wide',),
                   'fields': ('title','title3', 'title4', 'title5', 'title6', 'title7',
                              'title8', 'category', 'title10', 'title11', 'title12', 'title13',                           'title14', 'title16', 'title17', 'title18', 'title19', 'title20',                           'title21','title22')
                }),
             )
          
             list_display = ['title', 'user']
             raw_id_list_displayfields = ('user',)
             search_fields = ['title', 'user__username']
          
             def save_model(self, request, obj, form, change):
                if form.is_valid():
                   if not request.user.is_superuser or not form.cleaned_data["user"]:
                      obj.user = request.user
                      obj.save()
                   elif form.cleaned_data["user"]:
                      obj.user = form.cleaned_data["user"]
                      obj.save()
          
             def preprocess_list_display(self, request):
                if 'user' not in self.list_display:
                   self.list_display.insert(self.list_display.__len__(), 'user')
                if not request.user.is_superuser:
                   if 'user' in self.list_display:
                      self.list_display.remove('user')
          
             def preprocess_search_fields(self, request):
                if 'user__username' not in self.search_fields:
                   self.search_fields.insert(self.search_fields.__len__(), 'user__username')
                if not request.user.is_superuser:
                   if 'user__username' in self.search_fields:
                      self.search_fields.remove('user__username')
          
             def changelist_view(self, request, extra_context=None):
                self.preprocess_list_display(request)
                self.preprocess_search_fields(request)
                return super(EntryAdmin, self).changelist_view(request)
          
             def get_queryset(self, request):
                if request.user.is_superuser:
                   return super(EntryAdmin, self).get_queryset(request)
                else:
                   qs = super(EntryAdmin, self).get_queryset(request)
                   return qs.filter(user=request.user)
          
             def get_fieldsets(self, request, obj=None):
                if request.user.is_superuser:
                   return super(EntryAdmin, self).get_fieldsets(request, obj)
                return self.user_fieldsets
          
          admin.site.register(Excels, ExcelsAdmin)
          
            Naum74
            • Қар. 14, 2020, 9:33 Т.Ж.

            ...ах да и EntryAdmin я заменил везде на ExcelsAdmin.
            Ваш вариант с названием полей:
            n = models.CharField('Номер заказа', max_length=20)
            created_at = models.DateTimeField('Дата создания заказа', auto_now_add=True)
            modified_at = models.DateTimeField('Дата внесения изменения', auto_now=True)
            contragent = models.CharField('Контрагент', max_length=50)
            ...

            я обязательно перенесу в проект - он очень хорош!

              Илья Чичак
              • Қар. 14, 2020, 9:38 Т.Ж.

              я забыл описать один момент:
              при определении get_readonly_fields, нужно отловить момент, что этих полей может не быть при вызове super

              поэтому я бы, помимо всего прочего описал бы, какие есть поля и все их добавил в readonly:

              class ArticleAdmin(admin.ModelAdmin)
                  first_group_fields = ('...', '...', '...')
                  second_group_fields = ('...', '...', '...')
                  ...
              
                  fields = ('...', '...', ...)
                  readonly_fields = fields
              
                  def get_readonly_fields(self, request, obj=None)
                      base_readonly_fields = super().get_readonly_fields(request, obj)
                      if request.user.has_perm('can_edit_first_group'):
                          fields = [f for f in base_readonly_fields if f in self.first_group_fields]
                      elif request.user.has_perm('can_edit_second_group'):
                          fields = [f for f in base_readonly_fields if f in self.second_group_fields]
                      ...
                      return fields
              
                Илья Чичак
                • Қар. 14, 2020, 1:55 Т.Қ.

                по поводу связи с создавшим пользователем:

                class Articles(models.Model):
                    created_by = models.ForeignKey(get_user_model(), related_name='atricles', on_delete=models.SET_NULL, null=True)
                

                а в save_model() админки сделать так:

                    def save_model(self, request, obj, form, change):
                        obj = super().save_model(request, obj, form, change)
                        if obj.created_by is None:
                            obj.created_by = request.user
                            obj.save(update_fields=['created_by'])
                
                    def get_queryset(self, request):
                        queryset = super().get_queryset(request)
                        if request.user.has_perm('can_edit_first_group'):
                            return queryset.filter(created_by=request.user)
                        return queryset
                

                вероятно, нужно лучше продумать название пермишшенов. в идеале, это должны быть отдельные пермишшены

                я думаю, лучше сделать пермишшены:
                can_view_own_articles
                can_view_all_articles
                can_edit_own_articles
                can_edit_all_articles
                ...

                так же можно воспользоваться методами has_change_permissions, has_delete_permission и т.д.

                в общем, есть смысл почитать документацию на классы админки - они дают более, чем гибкие механизмы управления правами доступа

                  Naum74
                  • Қар. 15, 2020, 2:34 Т.Ж.

                  Илья, ЗДРАВСТВУЙТЕ!
                  Уже хочется поскорее добраться до самого компа на котром проект и попробовать все что вы мне предложили.
                  Прочитал я про команду ForeignKey! Действительно очень дельное предложение вы сделали!
                  Получится, что на определенные ячейки я предоставлю выбор и это будет выглядеть как"множественный выбор", останется просто выбрать определенное наименование уже внесенное в другой класс таблицы! Прекрасно!

                  Насчет того чтобы КАЖДЫЙ пользователь 1-й группы мог видеть ТОЛЬКО свою заведенную строку со всеми столбцами, а пользователи 2-й и 3-й группы могли видеть ВСЕ строки ВСЕХ авторов 1-й группы, могу понять то, что вы написали, в формате подставления функций (вставки вашего кода выделю "------" в исходном коде):

                  user_fieldsets = (
                       (None, {
                           'classes': ('wide',),
                           'fields': ('title','title3', 'title4', 'title5', 'title6', 'title7',
                                      'title8', 'category', 'title10', 'title11', 'title12', 'title13',                           'title14', 'title16', 'title17', 'title18', 'title19', 'title20',                           'title21','title22')
                        }),
                     )
                  
                     list_display = ['title', 'user']
                     raw_id_list_displayfields = ('user',) - что это ????? на что влияет???
                     search_fields = ['title', 'user__username']
                  
                     def save_model(self, request, obj, form, change):
                        if form.is_valid():
                           if not request.user.is_superuser or not form.cleaned_data["user"]:
                              obj.user = request.user
                              obj.save()
                           elif form.cleaned_data["user"]:
                              obj.user = form.cleaned_data["user"]
                              obj.save()
                    #----------------------------------------------------
                          obj = super().save_model(request, obj, form, change)
                          if obj.created_by is None:
                              obj.created_by = request.user
                              obj.save(update_fields=['created_by'])
                    #-----------------------------------------------------
                  
                     def preprocess_list_display(self, request):
                        if 'user' not in self.list_display:
                           self.list_display.insert(self.list_display.__len__(), 'user')
                        if not request.user.is_superuser:
                           if 'user' in self.list_display:
                              self.list_display.remove('user')
                  
                     def preprocess_search_fields(self, request):
                        if 'user__username' not in self.search_fields:
                           self.search_fields.insert(self.search_fields.__len__(), 'user__username')
                        if not request.user.is_superuser:
                           if 'user__username' in self.search_fields:
                              self.search_fields.remove('user__username')
                  
                     def changelist_view(self, request, extra_context=None):
                        self.preprocess_list_display(request)
                        self.preprocess_search_fields(request)
                        return super(ExcelsAdmin, self).changelist_view(request)
                  
                     def get_queryset(self, request):
                        if request.user.is_superuser:
                           return super(ExcelsAdmin, self).get_queryset(request)
                        else:
                           qs = super(ExcelsAdmin, self).get_queryset(request)
                           return qs.filter(user=request.user)
                    #---------------------------------------------------------
                  
                          if request.user.has_perm('can_edit_first_group'):
                              return queryset.filter(created_by=request.user)
                          return queryset
                    #---------------------------------------------------------
                  
                     def get_fieldsets(self, request, obj=None):
                        if request.user.is_superuser:
                           return super(ExcelsAdmin, self).get_fieldsets(request, obj)
                        return self.user_fieldsets
                  
                  admin.site.register(Excels, ExcelsAdmin)
                  

                  тут вот есть некая переменная: raw_id_list_displayfields = ('user',) - что это ????? на что влияет??? не могу нигде в документах найти.

                  Получается, по коду, 'can_edit_first_group' дает возможность пользователям 2-й и 3-й группы редактирования записей внесенных пользователями 1-й группы? Но имнужен только просмотр...
                  Или я не прав? Наверное я запутался вкоде и не пойму...

                    Пікірлер

                    Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
                    Кіріңіз немесе Тіркеліңіз
                    Г

                    C++ - Тест 001. Первая программа и типы данных

                    • Нәтиже:66ұпай,
                    • Бағалау ұпайлары-1
                    t

                    C++ - Тест 001. Первая программа и типы данных

                    • Нәтиже:33ұпай,
                    • Бағалау ұпайлары-10
                    t

                    Qt - Тест 001. Сигналы и слоты

                    • Нәтиже:52ұпай,
                    • Бағалау ұпайлары-4
                    Соңғы пікірлер
                    G
                    GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
                    Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
                    d
                    dblas5Шілде 5, 2024, 11:02 Т.Ж.
                    QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
                    k
                    kmssrАқп. 8, 2024, 6:43 Т.Қ.
                    Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                    АК
                    Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
                    Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                    Енді форумда талқылаңыз
                    Evgenii Legotckoi
                    Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
                    добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
                    F
                    FynjyШілде 22, 2024, 4:15 Т.Ж.
                    при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
                    BlinCT
                    BlinCTМаусым 25, 2024, 1 Т.Ж.
                    Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
                    BlinCT
                    BlinCTМамыр 5, 2024, 5:46 Т.Ж.
                    Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
                    Evgenii Legotckoi
                    Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
                    Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

                    Бізді әлеуметтік желілерде бақылаңыз