MySQL-owe widoki w Django

Podziel się z innymi!

    Niniejszy artykuł traktuje o widokach w bazie danych (konkretnie w MySQL) i możliwości tworzenia modeli do tychże widoków z użyciem ORM-a framewokra Django. Zwracam na to uwagę aby nie pomylić – pomimo zbieżności nazw – widoków SQL-owych z widokami (views.py) Django.

    Czym są właściwie widoki w relacyjnej bazie danych i po co się je tworzy wyjaśnia – skądinąd świetnie napisany rozdział pod tytułem „Widoki” w przygotowanym przez Heliona kursie MySQL.

    W jednym z projektów, nad którymi pracuję stanąłem przed potrzebą stworzenia widoku w MySQL-u. Postanowiłem więc zdefiniować taki widok, stworzyć do niego model w Django i posługiwać się jak dowolnym innym modelem z tym, że tylko do odczytu. ORM w Django jest jednym z najbardziej rozbudowanych i zaawansowanych narzędzi tego typu mimo to ma swoje ograniczenia dlatego też nie byłem pewien czy uda mi się zrealizować to co sobie założyłem. Okazało się to możliwe aczkolwiek dopiero od wersji 1.3 frameworka Django.

    Poniżej prezentuję jak zdefiniować prawidłowo model dla widoku. Na potrzeby tego artykułu stworzyłem możliwie proste przykłady aby pokazać mechanizm. Skonstruowany przeze mnie widok był daleko bardziej rozbudowany.

    Załużmy, że mamy nowy projekt django a w nim aplikację do prezentacji newsów.

    news/models.py
    from django.db import models
    from django.contrib.auth.models import User
     
    class Article(models.Model):
        name = models.CharField(max_length=255)
        slug = models.SlugField(unique=True, max_length=100)
        text = models.TextField()
        added_by = models.ForeignKey(User)
        added_on = models.DateTimeField(auto_now_add=True)
        updated_on = models.DateTimeField(auto_now=True)
        is_active = models.BooleanField(default=True)
    news/admin.py
    from news.models Article
    from django.contrib import admin
     
    class ArticleAdmin(admin.ModelAdmin):
        list_filter = ['is_active']
        list_display = ('name', 'added_by', 'added_on', 'updated_on', 'is_active')
        prepopulated_fields = {'slug': ('name',)}
        search_fields = ('name', )
     
    admin.site.register(Article, ArticleAdmin)

    Z pewnych powodów potrzebujemy widoku, który prezentuje się następująco:

    DROP VIEW IF EXISTS `users_newsview`;
    CREATE VIEW `users_newsview` AS
        SELECT 0 AS id, n.id AS news_id, n.name AS news_name, n.added_by_id AS user_id, u.username
        FROM news_article AS n
        INNER JOIN auth_user AS u ON u.id = n.added_by_id;

    Tworzymy do niego model, który będzie tylko do odczytu.

    users/models.py
    from django.db import models
    from news.models import Article
    from django.contrib.auth.models import User
     
    class NewsView(models.Model):
     
        class Meta:
            managed = False
     
        news = models.ForeignKey(Article, null=True, blank=True, on_delete=models.DO_NOTHING)
        news_name = models.CharField(max_length=255, null=True, blank=True)
        user = models.ForeignKey(User, null=True, blank=True, on_delete=models.DO_NOTHING)
        username = models.CharField(max_length=255, null=True, blank=True)

    O tym, że obiekty modelu „NewsViews” są niemodyfikowalne decyduje Zdefniowany w podklasie „Meta” parametr „managed = False”. Innym bardzo istotnym elementem jest dodanie do definicji kluczy obcych „on_delete=models.DO_NOTHING”. Django w przypadku kluczy obcych domyślnie emuluje zachowanie „on delete cascade”. Możliwość zmiany tego zachowania Django wspiera dopiero od wersji 1.3. Jeśli nie zmienimy sposobu w jaki Django ma postępować z elementami powiązanymi w trakcie usuwania danego obiektu to przy próby usunięcia newsa, albo usera zostanie zgłoszony błąd gdyż próba usunięcia także rekordu widoku z oczywistych względów się niepowiedzie.

    W starszej wersji frameworka Django też można stworzyć model do widoku, ale trzeba w nim zrezygnować z tworzenia relacji do innych obiektów i zamiast pól „news” i „user” zdefiniować pola typu integer „news_id” oraz „user_id”. Jak się domyślacie rozwiązanie to jest o wiele mniej wygodne.

    Podziel się z innymi!