Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
mvc
#1
Is it possible to have a controller to control two different views?
I wasn't really wanting to post the whole code but, to show the goal.
I have four buttons that will open a toplevel window. My goal is to get this window to interact with the controller.
Any thoughts or suggestion are appreciated.


I've updated the code with the solution I found. Also added images of my progress

card.json
{
    "wizard": [
        {
            "class": "wizard",
            "name": "tabol",
            "age": 23,
            "skills": {
                "fireball": 20,
                "meteor": 15
            }
        },
        {
            "class": "wizard",
            "name": "zac",
            "age": 25,
            "skills": {
                "fireball": 10,
                "meteor": 5
            }
        },
        {
            "class": "wizard",
            "name": "ralph",
            "age": 20,
            "skills": {
                "fireball": 15,
                "meteor": 5
            }
        }
    ],
    "cleric": [
        {
            "class": "cleric",
            "name": "harold",
            "age": 18,
            "skills": {
                "heal": 15,
                "heal all": 20
            }
        }
    ],
    "warrior": [
        {
            "class": "warrior",
            "name": "tony",
            "age": 25,
            "skills": {
                "knives": 8,
                "hand to hand": 20,
                "swords": 15
            }
        }
    ],
    "mage": [
        {
            "class": "mage",
            "name": "henry",
            "age": 21,
            "skills": {
                "bolt": 23,
                "dart": 16
            }
        }
    ]
}
backend.py
from os import path
import json
from itertools import chain

file = 'card.json'

def _create_database(file):
    if path.exists(file):
        pass
    else:
        with open(file, 'w') as out_file:
            characters = {}
            json.dump(characters, out_file, indent=4)

def _create_class(file, character_class):
    '''
    Function checks the json file for already existing classes.
    If one exist it return a message. If one does not exist it
    will create the class
    '''
    with open(file, 'r+') as json_file:
        data = json.load(json_file)
        if character_class in data:
            print('Class already exists')
        else:
            data[character_class] = []
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            print(f'Class {character_class} created')

def _check_name(file, name):
    with open(file, 'r') as json_file:
        data = json.load(json_file)
        if name.lower() in [c.get('name') for c in chain(*data.values())]:
            return True
        return False

def _create(file, character):
    check = _check_name(file, character['name'])
    if check:
        print(f'{character["name"].title()} already exists in the database.')
    else:
        with open(file, 'r+') as json_file:
            data = json.load(json_file)
            data[character['class']].append(character)
            json_file.seek(0)
            json.dump(data, json_file, indent=4)
            print(f'{character["name"].title()} has been created.')

def _delete(file, name):
    with open(file, 'r+') as json_file:
        data = json.load(json_file)
        ok = _check_name(file, name)
        if ok:
            for _ in data:
                for i in range(len(data[_])):
                    if name.lower() in data[_][i]['name']:
                        data[_].pop(i)

            with open(file, 'w') as json_file:
                json.dump(data, json_file, indent=4)
            print(f'{name.title()} has been deleted')
        else:
            print(f'{name.title()} is not in the database')

def _get_character(file, name):
    '''
    This function will return all related data to a character entered.
    '''
    with open(file, 'r+') as json_file:
        data = json.load(json_file)
        for _ in data:
            for i in range(len(data[_])):
                if name.lower() == data[_][i]['name']:
                    character = data[_][i]
                    return character

    return f'{name.title()} does not exist.'

def _get_all(file):
    with open(file, 'r') as json_file:
        data = json.load(json_file)
        characters = [c for c in chain(*data.values())]
        return characters

def _update(file, old, new):
    character = old.copy()
    character.update(new)
    _delete(file, old['name'])
    _create(file, character)
    print(f'{character["name"].title()} has been updated.')

def get_classes(file):
    classes = []
    with open(file, 'r') as json_file:
        data = json.load(json_file)
        for _ in data:
            classes.append(_.title())
    return classes

def get_character_by_guild(file, guild):
    with open(file, 'r') as json_file:
        data = json.load(json_file)
        names = []
        if guild.lower() in [character.get('class') for character in chain(*data.values())]:
            for character in data[guild]:
                if character:
                    names.append(character['name'])
        return names
card.py
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
from tkinter import ttk, messagebox
import json
import backend
from os import sys

file = 'card.json'

class BasicModel:
    def __init__(self, file):
        self.file = file
        backend._create_database(self.file)

    def create_class(self, character_class):
        backend._create_class(self.file, character_class)

    def create_character(self, character):
        backend._create(self.file, character)

    def get_character(self, name):
        return backend._get_character(self.file, name)

    def get_all(self):
        return backend._get_all(self.file)

    def update(self, old, new):
        return backend._update(self.file, old, new)

    def get_classes(self):
        return backend.get_classes(self.file)

    def get_character_by_guild(self, guild):
        return backend.get_character_by_guild(self.file, guild)


class View:
    def __init__(self, parent):
        self.parent = parent
        self.parent.title('Card Collection')
        self.parent.columnconfigure(0, weight=1)
        self.parent.rowconfigure(0, weight=1)

        # Container
        container = tk.Frame(self.parent)
        container['borderwidth'] = 1
        container['highlightbackground'] = 'lightgray'
        container['highlightcolor'] = 'lightgray'
        container['highlightthickness'] = 1
        container.grid(column=0, row=0, sticky='news', padx=8, pady=8)
        container.grid_columnconfigure(0, weight=3)

        # Header
        self.title_label = tk.Label(container, text='Card Collection')
        self.title_label['bg'] = 'gray86'
        self.title_label['font'] = ('"" 16 bold')
        self.title_label['relief'] = 'groove'
        self.title_label.grid(column=0, row=0, sticky='new', pady=4, padx=4, ipady=4)

        # Search Frame
        search_frame = tk.Frame(container, padx=4, pady=4)
        search_frame['highlightcolor'] = 'gray80'
        search_frame['highlightbackground'] = 'gray80'
        search_frame['highlightthickness'] = 1
        search_frame.grid(column=0, row=1, sticky='news', pady=4, padx=4)

        # Search Boxes
        show_ = tk.StringVar()
        self.menu = ttk.Combobox(search_frame, textvariable=show_)
        self.menu.grid(column=0, row=0, sticky='new')

        # Mid Frame Container
        mid_container = tk.Frame(container)
        mid_container.grid(column=0, row=2, sticky='new', pady=4, padx=4)
        mid_container.grid_columnconfigure(0, weight=3, uniform='box')
        mid_container.grid_columnconfigure(1, weight=3, uniform='box')

        list_container = tk.Frame(mid_container)
        list_container.grid(column=0, row=0, sticky='news')
        list_container.grid_columnconfigure(0, weight=3)

        # Textbox for left side
        self.left_listbox = tk.Listbox(list_container, height=15, selectmode='single')
        self.left_listbox.grid(column=0, row=0, sticky='news')

        scrollbar = tk.Scrollbar(list_container)
        scrollbar.grid(column=1, row=0, sticky='ns')
        self.left_listbox.configure(yscrollcommand = scrollbar.set)
        scrollbar.config(command = self.left_listbox.yview)

        # Right Side
        self.label_frame = tk.Frame(mid_container, pady=8)
        self.label_frame['highlightcolor'] = 'gray80'
        self.label_frame['highlightbackground'] = 'gray80'
        self.label_frame['highlightthickness'] = 1
        self.label_frame.grid(column=1, row=0, sticky='news')

        # Button Conainer
        btn_container = tk.Frame(container)
        btn_container['highlightcolor'] = 'gray86'
        btn_container['highlightbackground'] = 'gray86'
        btn_container['highlightthickness'] = 1
        btn_container.grid(column=0, row=3, sticky='news', padx=4, pady=4)
        for i in range(4):
            btn_container.grid_columnconfigure(i, weight=3, uniform='buttons')

        self.add = tk.Button(btn_container, text='Add')
        self.add['cursor'] = 'hand2'
        self.add.grid(column=0, row=0, sticky='new', padx=2, pady=4)

        self.edit = tk.Button(btn_container, text='Edit')
        self.edit['cursor'] = 'hand2'
        self.edit.grid(column=1, row=0, sticky='new', padx=2, pady=4)

        self.delete = tk.Button(btn_container, text='Delete')
        self.delete['cursor'] = 'hand2'
        self.delete.grid(column=2, row=0, sticky='new', padx=2, pady=4)

        self.exit = tk.Button(btn_container, text='Exit')
        self.exit['bg'] = 'tomato'
        self.exit['command'] = sys.exit
        self.exit['cursor'] = 'hand2'
        self.exit.grid(column=3, row=0, sticky='new', padx=2, pady=4)


class TopWindow:
    def __init__(self, parent, title=None, target=None):
        self.parent = parent
        self.window = tk.Toplevel()
        self.window.geometry('400x300+250+250')
        self.window.title(title.title())

        self.window.columnconfigure(0, weight=1)
        self.window.rowconfigure(0, weight=1)

        self.container = tk.Frame(self.window, padx=8)
        self.container.grid(column=0, row=0, sticky='new')
        self.container.columnconfigure(0, weight=0)
        self.container.columnconfigure(1, weight=3)

        if target == 'create':
            self.create_character()

        if target == 'edit':
            self.edit_character()

        if target == 'delete':
            self.delete_character()


    def create_character(self):
        self.widget_container()

    def edit_character(self):
        ttk.Style().configure('field.TEntry', padding='4 2 2 2')

        self.agevar = tk.StringVar()

        name = ttk.Label(self.container, text='Name:', anchor='w')
        name.grid(column=0, row=1, sticky='new', padx=(4, 0), pady=(8,0))

        self.name_field = ttk.Entry(self.container, style='field.TEntry')
        self.name_field.grid(column=1, row=1, sticky='new', padx=(8, 0), pady=(8,0))

        guild = ttk.Label(self.container, text='Class:', anchor='w')
        guild.grid(column=0, row=2, sticky='new', padx=(4, 0), pady=(8, 0))

        self.guild_field = ttk.Entry(self.container, style='field.TEntry')
        self.guild_field.grid(column=1, row=2, sticky='new', padx=(8, 0), pady=(8, 0))

        age = ttk.Label(self.container, text='Age')
        age.grid(column=0, row=3, sticky='new', padx=(4, 0), pady=(8, 0))

        self.age_field = ttk.Entry(self.container, textvariable=self.agevar, style='field.TEntry')
        self.age_field.grid(column=1, row=3, sticky='new', padx=(8, 280), pady=(8, 0))

        skills = ttk.Label(self.container, text='Skills:', anchor='w')
        skills.grid(column=0, row=4, sticky='new', padx=(4, 0), pady=(8, 0))

        self.skills_field = ttk.Entry(self.container, style='field.TEntry')
        self.skills_field.grid(column=1, row=4, sticky='new', padx=(8, 0), pady=(8, 0))

        self.agevar.trace_add('write', self.limit_size)


    def delete_character(self):
        print('deleting')

    def limit_size(self, *args):
        agevar = self.agevar.get()
        if len(agevar) > 3:
            messagebox.showerror(title='Error!', message='Age can\'t be more than 3 digits long')
            self.age_field.delete(0, tk.END)
        try:
            agevar = int(agevar)
        except:
            messagebox.showerror('Error!', 'Only numbers can be entered.')
            self.age_field.delete(0, tk.END)



class Controller:
    def __init__(self, model, view):
        self.model = model
        self.view = view

        data = self.model.get_classes()
        data.insert(0, 'Show by ...')
        data.insert(1, 'Names')
        self.view.menu['values'] = data
        self.view.menu.current(0)
        self.view.menu.bind('<<ComboboxSelected>>', self.listbox_update)

    def show_all(self):
        characters = self.model.get_all()
        for i, character in enumerate(characters):
            name = f'{" ":<2}{character["name"].title()}'
            self.view.left_listbox.insert(i, name)
        self.view.left_listbox.bind('<Double-Button-1>', self.show_character)
        self.view.left_listbox.selection_set(0)
        char = self.view.left_listbox.get(self.view.left_listbox.curselection())
        self.first_name(char)

    def show_guild(self, guild):
        characters = self.model.get_character_by_guild(guild.lower())
        for i, character in enumerate(characters):
            name = f'{" ":<2}{character.title()}'
            self.view.left_listbox.insert(i, name)
        self.view.left_listbox.selection_set(0)
        char = self.view.left_listbox.get(self.view.left_listbox.curselection())
        self.view.left_listbox.bind('<Double-Button-1>', self.show_character)
        self.first_name(char)

    def first_name(self, name):
        character = name.lower().strip()
        data = self.model.get_character(character)

        frame = self.view.label_frame
        frame.grid_columnconfigure(0, weight=3)
        row = 0
        mylist = []
        for key, value in data.items():
            if isinstance(value, dict):
                for k, v in value.items():
                    mylist.append(f'{k}: {v}')
                value = ', '.join(mylist)
            elif isinstance(value, int):
                value = value
            else:
                value = value.title()
            line = tk.Label(frame, text=f'{key.upper()}: {value}', anchor='w')
            line['relief'] = 'groove'
            line.grid(column=0, row=row, sticky='new', padx=8, pady=1)
            row += 1

    def show_character(self, event):
        character = self.view.left_listbox.get(self.view.left_listbox.curselection())
        character = character.lower().strip()
        data = self.model.get_character(character)

        frame = self.view.label_frame
        frame.grid_columnconfigure(0, weight=3)
        row = 0
        mylist = []
        for key, value in data.items():
            if isinstance(value, dict):
                for k, v in value.items():
                    mylist.append(f'{k}: {v}')
                value = ', '.join(mylist)
            elif isinstance(value, int):
                value = value
            else:
                value = value.title()
            self.line = tk.Label(frame, text=f'{key.upper()}: {value}', anchor='w')
            self.line['relief'] = 'groove'
            self.line.grid(column=0, row=row, sticky='new', padx=8, pady=1)
            row += 1

    def listbox_update(self, event):
        search = self.view.menu.get()
        guilds = self.model.get_classes()
        self.view.left_listbox.delete(0, tk.END)

        if search.lower() == 'names':
            self.show_all()
        elif search in guilds:
            self.show_guild(search)
        else:
            pass

        name = self.view.left_listbox.get(self.view.left_listbox.curselection()).strip()
        self.view.edit['command'] = lambda: Controller2(BasicModel(file), \
        TopWindow(self.view.parent, title='edit character', target='edit'), name, target='edit')


class Controller2(Controller):
    def __init__(self, model, topwindow, name=None, target=None):
        self.model = model
        self.topwindow = topwindow
        self.name = name

        if self.name:
            data = self.model.get_character(self.name)
            if target == 'edit':
                print(data)
                self.topwindow.name_field.insert(0, data['name'])
                self.topwindow.guild_field.insert(0, data['class'])
                self.topwindow.age_field.insert(0, data['age'])
                self.topwindow.skills_field.insert(0, data['skills'])

            if target == 'delete':
                print('delete character here')



def main():
    app = tk.Tk()
    app.geometry('600x400+200+200')
    app.resizable(False, False)
    controller = Controller(BasicModel(file), View(app))
    app.mainloop()


if __name__ == '__main__':
    main()
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags


Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020