Wie einen Einzelwert in bestehende Zeile einfügen?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
arien101
User
Beiträge: 9
Registriert: Dienstag 23. Dezember 2008, 12:43

Dienstag 23. Dezember 2008, 15:03

Hallo,

vorneweg: Ich bin totaler Anfänger was programmieren und auch was Datenbanken angeht. Da ich durch die Wirtschaftskrise gerade viel Zeit habe, wollte ich mit beides beibringen. Dazu fiel meine Wahl auf Pyhton, da es hier viele verständliche Tutorials gab.

Zur Übung bastle ich mir ein kleines Pseudo-Rollenspiel. Die Charakter-Generierung habe ich durch und will die Ergebnisse jetzt in eine Datenbank speichern. Etwa so:

player_id | login | pass | chr_name | chr_job |
player_1 | meinlogin | passwort | John | Doe |
player_2 | deinlogin | wortpass | Conan | der Barbar |

Nur mit dem Unterschied, dass zuerst an einer Stelle login und pass abgefordert und gespeichert werden und dann später an einer anderen Stelle im Programm chr_name und chr_job.

Sobald ich login und pass habe, schreibe ich momentan folgendes in die Datenbank:

Code: Alles auswählen

data_inserter = {
"player_id" : player_id, "login_name" : login_name, "login_password" : login_password
}
        
sql_inserter = "INSERT INTO players VALUES (:player_id, :login_name , :login_password, null, null)"
        
cursor.execute(sql_inserter, data_inserter)
Damit möchte ich erreichen, dass die beiden letzten Spalten chr_name und chr_job unbelegt bleiben.

Jetzt möchte ich einem zweiten Schritt etwas nur in diese letzten beiden Spalten der Zeile schreiben. Wie mache ich das?

Bin für jeden Tip dankbar!
Birne94
User
Beiträge: 90
Registriert: Freitag 28. November 2008, 15:18
Kontaktdaten:

Dienstag 23. Dezember 2008, 15:30

Code: Alles auswählen

update players set chr_name=WERT, chr_jop=WERT2 where player_id=PLAYERID
arien101
User
Beiträge: 9
Registriert: Dienstag 23. Dezember 2008, 12:43

Dienstag 23. Dezember 2008, 15:37

Super, das hat geklappt.

Dankeschön :-)
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Dienstag 23. Dezember 2008, 15:39

Hint: Unter Python nutzt man öfter ORMs als SQL direkt. Mit Klassen und Objekten arbeiten ist nun doch etwas angenehmer als mit Strings.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
arien101
User
Beiträge: 9
Registriert: Dienstag 23. Dezember 2008, 12:43

Dienstag 23. Dezember 2008, 15:47

Hi Leonidas,

ja, ich hatte vorher das Forum durchsucht und deine Hinweise auf SQLALchemy entdeckt. Da ich aber bisher 0 Ahnung von SQL habe, wollte ich erstmal mit SQL rumprobieren.

Wenn ich das halbwegs verstanden habe, würde ich dann mit einem ORM weitermachen.

Bisher bin ich noch damit beschäftigt mich durch alle möglichen Tutorials durchzubeißen. Ich bin eigentlich BWLer und tu mich mit Programmieren & Logik deshalb etwas schwer :wink:
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Dienstag 23. Dezember 2008, 16:40

Bei Rollenspiel werde ich hellhörig, allerdings scheint mir das als Beispiel für eine Datenbank-zentrische Anwendung recht weit hergeholt -- oder aber ich habe eine komplett andere Vorstellung davon, was das sein soll. Ich würde da an Ultima, The Bard's Tale oder Gothic denken.

Letztlich simuliert ein Computer-Rollenspiel ja Objekte in einer virtuellen Welt und dafür wäre objektorientierte Programmierung, wie sie mit Python möglich ist, durchaus geeignet.

Relationale Datenbanken (mysql und so) und objektorientierte Programmierung passen allerdings nicht optimal zusammen. Ein sogenannter objektrelationaler Mapper (ORM), kann helfen, den Schmerz zu verringern, doch den Stein der Weisen hat da noch niemand gefunden. Objektorientierte Datenbanken sind hingegen wenig verbreitet.

Ganz pur "bare bone" SQL benutzen würde ich allerdings nur, wenn ich's lernen will ;) Andernfalls wird das ganz schnell so umständlich und fehleranfällig, dass man sich da mit einem ORM helfen lassen will. Da hättest du dann vielleicht etwas wie

Code: Alles auswählen

# Django
player = Player.objects.create(name="Conan", job="König von Aquilonien")
player.save()

--oder--

# SQLalchemy
session = Session()
session.add(Player("Conan", "König von Aquilonien"))
session.commit()
arien101, ich glaube, du suchst die SQL-Anweisung UPDATE, bin mir aber nicht sicher, ob ich wirklich verstehe, was du willst. Ein ORM wie SQLalchemy kümmert sich darum, automatisch den richtigen SQL-Befehl zu verwenden, je nachdem was und wie du es änderst.

Ich würde aber empfehlen, erstmal nur mit Python zu starten. Dort kann man ein Netz von Objekten, wie sie bei einer virtuellen Welt üblicherweise entstehen, dann mit "pickle" auf einen Schlag speichern und später wieder laden. Dann musst du dir um das einzelne Speichern der Daten überhaupt keine Gedanken machen und kannst dich erst mal auf den kreativen Prozess der Programmierung des Spiel konzentrieren.

Stefan
DasIch
User
Beiträge: 2437
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Dienstag 23. Dezember 2008, 17:08

Etwas OT:
arien101 hat geschrieben:Sobald ich login und pass habe, schreibe ich momentan folgendes in die Datenbank:[...]
Wenn du wirklich dass Passwort selbst in die Datenbank schreiben willst ist das eine schlechte Idee. Keiner Nutzer möchte gerne sein Passwort ungeschützt jemand anders geben. Du solltest den Hash speichern, wobei du vorher dass Passwort mit mit "salt" verändern solltest. Etwa so:

Code: Alles auswählen

from string import digits, ascii_letters, punctuation
import hmac
import random

def create_salt(chars=digits+ascii_letters+punctuation, length=8):
    if length <= 0:
        raise ValueError('Can\'t create a str with less than 1 character')
    return ''.join(random.choice(chars) for _ in xrange(length))

def create_password_hash(password, salt=None):
    salt = salt or create_salt()
    salt = salt.replace('$', '')
    h = hmac.new(password, salt).hexdigest()
    return 'sha1${salt}${hash}'.format(salt=salt, hash=h)

def check_password_hash(password, password_hash):
    method, salt, h = password_hash.split('$')
    return h == hmac.new(password, salt).hexdigest()
Wie du es schaffst die Hashmethode optional zu wählen sei dir an dieser Stelle selbst überlassen.
[/ot]

Ich würde dir übrigens wie meine Vorgänger zu einem ORM wie SQLAlchemy raten, damit würdest du ein Model erstellen dass dann z.B. so aussieht.

Code: Alles auswählen

class Player(Model):
    __tablename__ = 'players'

    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(30))
    password_hash = Column(String(150))
    char_name = Column(String(30))
    char_job = Column(String(50))
    
    def __init__(self, username, password, char_name, char_job):
      self.username = username
      self.pasword_hash = create_password_hash(password)
      self.char_name = char_name
      self.char_job = char_job
Beim Login würdest du nach dem `username` in der Datenbank suchen, das Password überprüfen und dann das Player Objekt z.B. an ein Anwendungsobjekt binden.

Code: Alles auswählen

player = Player.query.filter_by(username=login_name).first()
if player and check_password_hash(login_password, player.password_hash):
    local.application.current_player = player
Du musst dir keine Sorgen ums Escapen machen, du musst dich nicht mit einer zweiten Sprache(SQL) herumplagen, du bist unabhängig von der Datenbankanbindung, dass ganze ist etwas "natürlicher" in der Verwendung und dein Code nicht vollgemüllt mit irgendwelchen Strings in denen SQL Anweisungen stehen.

P.S.:Ja, ich hatte etwas langeweile ;)
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

Dienstag 23. Dezember 2008, 17:29

@DasIch: Anstatt das Dollarzeichen später zu entfernen, kannst du die create_salt-Funktion auch gleich etwas umschreiben, damit man nicht erwünschte Zeichen angeben kann:

Code: Alles auswählen

#!/usr/bin/env python

import random
from string import letters, digits

def generate_salt(length=8, include=letters + digits, exclude="$"):
    chars = list(set(include) ^ set(exclude))
    return "".join(random.sample(chars, length))
arien101
User
Beiträge: 9
Registriert: Dienstag 23. Dezember 2008, 12:43

Mittwoch 24. Dezember 2008, 17:21

Hey,

danke für die vielen Antworten! Meine konkrete Frage war mit dem zweiten Post beantwortet. Ich suchte tatsächlich die SQL-Anweisung UPDATE. Damit klappt es jetzt auch.

sma schrieb:
Bei Rollenspiel werde ich hellhörig, allerdings scheint mir das als Beispiel für eine Datenbank-zentrische Anwendung recht weit hergeholt -- oder aber ich habe eine komplett andere Vorstellung davon, was das sein soll. Ich würde da an Ultima, The Bard's Tale oder Gothic denken.
Ja, so was ähnliches ist tatsächlich gemeint, aber es ist wirklich nur als Übungsaufgabe für mich gedacht. In dem Buch dessen Beispiele ich durcharbeite geht es um eine Bank, für die dann eine Konto-Klasse aufgebaut wird mit Methoden wie Einzahlung, Auszahlung usw. Das war mir zu trocken, deshalb versuche ich die ganzen Beispiele für mich so umzubasteln, dass sie auf ein Rollenspiel passen.

Der Grund warum ich SQL im Beispiel durcharbeite hat also weniger mit dem Rollenspiel zu tun, sondern mehr damit dass das Thema SQL halt im Tutorial vorkommt. Zum Thema "pickle" bin ich noch nicht durchgedrungen. Das hab ich nur überflogen.

@DasIch:
Danke für die Hinweise. Tatsächlich hatte ich mir schon überlegt wie ich mit dem Passwort umgehe, hatte das aber auf später verschoben.

Ingsgesamt bin ich von eurer Hilfsbereitschaft echt umgehauen. Ich werde mal versuchen das Feedback einzubauen und das Programm soweit zu schreiben, das es das macht was ich will:
Benutzerdaten für Charaktergenerierung abfragen und diese Infos in einer Datenbank speichern.

Sobald ich das geschafft habe poste ich das Ergebnis dann hier und dann habt ihr was zu lachen :wink:

PS: Frohe Weihnachten
arien101
User
Beiträge: 9
Registriert: Dienstag 23. Dezember 2008, 12:43

Samstag 27. Dezember 2008, 21:38

Hallo,

das Programm macht jetzt so ziemlich das was ich wollte. Daraufhin habe ich mir die Anmerkungen zur Passwortsicherheit von DasIch vorgenommen:
DasIch hat geschrieben:Etwas OT:
arien101 hat geschrieben:Sobald ich login und pass habe, schreibe ich momentan folgendes in die Datenbank:[...]
Wenn du wirklich dass Passwort selbst in die Datenbank schreiben willst ist das eine schlechte Idee. Keiner Nutzer möchte gerne sein Passwort ungeschützt jemand anders geben. Du solltest den Hash speichern, wobei du vorher dass Passwort mit mit "salt" verändern solltest. Etwa so:

Code: Alles auswählen

from string import digits, ascii_letters, punctuation
import hmac
import random

def create_salt(chars=digits+ascii_letters+punctuation, length=8):
    if length <= 0:
        raise ValueError('Can\'t create a str with less than 1 character')
    return ''.join(random.choice(chars) for _ in xrange(length))

def create_password_hash(password, salt=None):
    salt = salt or create_salt()
    salt = salt.replace('$', '')
    h = hmac.new(password, salt).hexdigest()
    return 'sha1${salt}${hash}'.format(salt=salt, hash=h)

def check_password_hash(password, password_hash):
    method, salt, h = password_hash.split('$')
    return h == hmac.new(password, salt).hexdigest()
Den Code habe ich mittlerweile verstanden und weiß jetzt auch wozu Hashes und Salts gut sind. Den Code habe ich dann eingebunden, erhalte aber folgende Fehlermeldung:

Please choose and enter a password: hulk
Traceback (most recent call last):
File "C:\Python26\game\character_generation.py", line 70, in <module>
player = Player(login_name, password_hash)
File "C:\Python26\game\classes.py", line 55, in __init__
cursor.execute(sql_inserter, data_inserter)
ProgrammingError: You must not use 8-bit bytestrings unless you use a text_factory that can interpret 8-bit bytestrings (like text_factory = str). It is highly recommended that you instead just switch your application to Unicode strings.


Bis zu Zeile 55 passiert folgendes:

Code: Alles auswählen

#! C:\\Python26\game
# -*- coding: iso-8859-15 -*-

import sqlite3
import random
import hmac
from string import letters, digits

def create_salt(length=8, include=letters + digits, exclude="$"):
    chars = list(set(include) ^ set(exclude))
    return "".join(random.sample(chars, length)) 

def create_password_hash(password):
    salt = create_salt()
    h = hmac.new(password, salt).hexdigest()
    return 'sha1${salt}${hash}'.format(salt=salt, hash=h)

def check_password_hash(password, password_hash):
    method, salt, h = password_hash.split('$')
    return h == hmac.new(password, salt).hexdigest()

class Player(object):
    Players_total = 0
    
    def __init__(self, login_name, password_hash):
        self.Login_name = login_name
        self.Login_password_hash = password_hash
        self.Chr_firstname = None
        self.Chr_lastname = None
        self.Chr_sex =  None
        self.Chr_occupation = None
        self.Chr_charisma = 0
        self.Chr_endurance = 0
        self.Chr_intelligence = 0
        self.Chr_knowledge = 0
        self.Chr_psyche = 0
        self.Chr_reflexes = 0
        self.Chr_experience = 0
        self.Chr_credits = 100
        self.Chr_fame = 0
        Player.Players_total += 1

        chr_experience = self.Chr_experience
        chr_credits = self.Chr_credits
        chr_fame = self.Chr_fame

        data_inserter = {
        "login_name" : login_name, "password_hash" : password_hash, "chr_experience" : chr_experience, "chr_credits" : chr_credits, "chr_fame" : chr_fame
        }
        
        sql_inserter = "INSERT INTO players VALUES (null, :login_name , :password_hash, null, null, null, null, null, null, null, null, null, null, :chr_experience, :chr_credits, :chr_fame)"
        
        connection = sqlite3.connect("players.db")
        cursor = connection.cursor()
        cursor.execute(sql_inserter, data_inserter)
        connection.commit()
Generell habe ich ein Verständnisproblem mit Unicode. Diese Zeile am Anfang, hat zumindest in IDLE für eine Ausgabe von Umlauten gesorgt:

Code: Alles auswählen

# -*- coding: iso-8859-15 -*-
Deshalb dachte ich, dass ich einzelne Strings nicht mehr in Unicode formatieren muss.

Allerdings weiß ich auch nicht wie ich u"irgendwas" zusammen mit raw_input benutze. Wie bringe ich raw_input dazu etwas als unicode anzunehmen?

Der Gesamtcode war leider zu lange um ihn hier im Forum abzubilden. Sonst hätte ich euch den jetzt zur Manöverkritik vorgelegt.
BlackJack

Samstag 27. Dezember 2008, 23:55

Was sollen die ganzen Präfixe bei den Namen? `Chr_*`? Char? Christian? Warum fangen Attributnamen mit einem Grossbuchstaben an?

Was machst Du mit `Player.Players_total`? Falls Du in der Klasse eine `__del__()`-Methode zum runterzählen hast: Vergiss es, das funktioniert nicht zuverlässig.

Man kann bei Datenstrukturen auch mal Zeilenumbrüche verwenden. `sql_inserter` und `data_inserter` haben komische Namen und die Zeilen sind zu lang.

Die `str`-Exemplare, die `raw_input()` liefert müsstest Du erst dekodieren. Die passende Kodierung darfst Du raten. :-) Man sollte auf jedenfall dem Benutzer erlauben die anzugeben.

Ich bin mir auch nicht so ganz sicher, ob die Klasse so besonders praktisch ist. Wie erstellt man ein `Player`-Exemplar für einen Spieler, der schon in der Datenbank vorhanden ist?
DasIch
User
Beiträge: 2437
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Sonntag 28. Dezember 2008, 00:48

Eventuell hilft dass Unicode Howto etwas Licht ins dunkel zu bringen.
arien101
User
Beiträge: 9
Registriert: Dienstag 23. Dezember 2008, 12:43

Sonntag 28. Dezember 2008, 01:56

BlackJack hat geschrieben:Was sollen die ganzen Präfixe bei den Namen? `Chr_*`? Char? Christian? Warum fangen Attributnamen mit einem Grossbuchstaben an?
chr soll die Abkürzung von "character" sein. Das Buch das ich durcharbeite lässt die Attributnamen mit Großbuchstaben anfangen. Dachte deshalb das muss so sein.
BlackJack hat geschrieben:Was machst Du mit `Player.Players_total`? Falls Du in der Klasse eine `__del__()`-Methode zum runterzählen hast: Vergiss es, das funktioniert nicht zuverlässig.
Damit wollte ich ursprünglich die Gesamtzahl aller Player zählen. Habe das dann aber über die Datenbank gemacht.

BlackJack hat geschrieben:Man kann bei Datenstrukturen auch mal Zeilenumbrüche verwenden. `sql_inserter` und `data_inserter` haben komische Namen und die Zeilen sind zu lang.
Danke für den Hinweis. War mir nicht sicher ob ich da irgendein Zeichen einfügen muss beim Umbruch.
BlackJack hat geschrieben:Die `str`-Exemplare, die `raw_input()` liefert müsstest Du erst dekodieren. Die passende Kodierung darfst Du raten. :-) Man sollte auf jedenfall dem Benutzer erlauben die anzugeben.
Oh, toll :?
BlackJack hat geschrieben:Ich bin mir auch nicht so ganz sicher, ob die Klasse so besonders praktisch ist. Wie erstellt man ein `Player`-Exemplar für einen Spieler, der schon in der Datenbank vorhanden ist?


Hmm, ja, das geht nicht. Der Grund warum ich hier überhaupt eine Klasse verwende war ja eigentlich, weil ich gerade an der entsprechenden Stelle im Buch war. Deshalb hatte ich mir wenig Gedanken über die Sinnhaftigkeit gemacht. Player war eben das einzige was für eine Klasse in Frage kam.

DasIch hat geschrieben: Eventuell hilft dass Unicode Howto etwas Licht ins dunkel zu bringen.
Hab ich schonmal gelesen, aber das hat leider nicht zum Verständnis geführt. Werd' ich nochmal lesen. Vielleicht kapier ich's beim 2. Mal.
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Sonntag 28. Dezember 2008, 02:19

arien101 hat geschrieben:
BlackJack hat geschrieben:Was sollen die ganzen Präfixe bei den Namen? `Chr_*`? Char? Christian? Warum fangen Attributnamen mit einem Grossbuchstaben an?
chr soll die Abkürzung von "character" sein. Das Buch das ich durcharbeite lässt die Attributnamen mit Großbuchstaben anfangen. Dachte deshalb das muss so sein.
Nein, das zeigt eher dass der Autor ungarische Notation nicht kapiert hat. Die in Python sowieso wenig Sinn macht.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
arien101
User
Beiträge: 9
Registriert: Dienstag 23. Dezember 2008, 12:43

Sonntag 28. Dezember 2008, 10:40

DasIch hat geschrieben:Eventuell hilft dass Unicode Howto etwas Licht ins dunkel zu bringen.
Nach dem 2. durchlesen habe ich jetzt versucht alle Strings in Unicode umzuwandeln. Besonders auch das eingegebene Passwort. Als Encoding habe ich iso-8859-15 genommen, da Europa ausreichen müsste. Daraufhin bekomme ich jetzt eine andere Fehlermeldung.

Code: Alles auswählen

while True:
    login_password = getpass.getpass("""
Please choose and enter a password: """).decode('iso-8859-15')
    if len(login_password) >= 4:
        break
    else:
        print """
Your password must be at least 4 characters long!.
"""
        continue

# Add random parameter to password and save it as a hash
password_hash = create_password_hash(login_password)
player = Player(login_name, password_hash)
Der resultierende Fehler:
Please choose and enter a password: beispiel
Traceback (most recent call last):
File "C:\Python26\game\character_generation.py", line 76, in <module>
password_hash = create_password_hash(login_password)
File "C:\Python26\game\classes.py", line 15, in create_password_hash
h = hmac.new(password, salt).hexdigest()
File "C:\Python26\lib\hmac.py", line 133, in new
return HMAC(key, msg, digestmod)
File "C:\Python26\lib\hmac.py", line 72, in __init__
self.outer.update(key.translate(trans_5C))
TypeError: character mapping must return integer, None or unicode


Was ist mit character mapping gemeint?
Antworten